package com.bestcem.xm.survey.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.NumberUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HtmlUtil;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.bestcem.bp.xm.user.grpc.v1.services.UserRole;
import com.bestcem.xm.common.core.enums.ValidationErrorCodeEnum;
import com.bestcem.xm.common.core.exception.BusinessException;
import com.bestcem.xm.common.core.uitls.EmailUtil;
import com.bestcem.xm.common.core.uitls.StringUtil;
import com.bestcem.xm.component.cache.annotation.XmCache;
import com.bestcem.xm.component.mybatis.service.impl.XmServiceImpl;
import com.bestcem.xm.component.security.context.SecurityContextHolder;
import com.bestcem.xm.componet.redis.service.RedisService;
import com.bestcem.xm.qdes.grpc.v1.services.*;
import com.bestcem.xm.survey.controller.vo.SurveyRespondentPageVo;
import com.bestcem.xm.survey.controller.vo.SurveyRespondentVo;
import com.bestcem.xm.survey.entity.*;
import com.bestcem.xm.survey.enums.RespondentStatusEnum;
import com.bestcem.xm.survey.grpc.client.DeliverGrpcClient;
import com.bestcem.xm.survey.grpc.client.MemberGrpcClient;
import com.bestcem.xm.survey.grpc.client.QdesGrpcClient;
import com.bestcem.xm.survey.grpc.client.UserGrpcClient;
import com.bestcem.xm.survey.grpc.client.dto.DeliverDto;
import com.bestcem.xm.survey.grpc.client.dto.GroupDto;
import com.bestcem.xm.survey.grpc.client.dto.MemberDto;
import com.bestcem.xm.survey.grpc.client.dto.QstructsDto;
import com.bestcem.xm.survey.mapper.*;
import com.bestcem.xm.survey.mq.dto.CreateQuotaDto;
import com.bestcem.xm.survey.mq.send.biz.SurveyMessageSendService;
import com.bestcem.xm.survey.service.SurveyColumnService;
import com.bestcem.xm.survey.service.SurveyCommonService;
import com.bestcem.xm.survey.service.SurveyQuotaConditionService;
import com.bestcem.xm.survey.service.SurveyRespondentService;
import com.google.common.collect.Lists;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.bestcem.xm.deliver.grpc.v1.services.DeliverExtparamsStatus.DELIVEREXTPARAMS_STATUS_START_VALUE;
import static com.bestcem.xm.openplatform.grpc.v1.services.ExtParamEnumManageStatus.EXTPARAMENUMMANAGE_STATUS_START_VALUE;
import static com.bestcem.xm.survey.enums.ExtparamDtypeDataEnum.EXTPARAM_DTYPE_DATA_BOOL;
import static com.bestcem.xm.survey.enums.ExtparamDtypeDataEnum.EXTPARAM_DTYPE_DATA_TIMESTAMP;
import static com.bestcem.xm.survey.grpc.v1.services.ColumnStatus.*;
import static com.bestcem.xm.survey.grpc.v1.services.RespondentStatus.*;

/**
 * @author guita
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class SurveyColumnServiceImpl extends XmServiceImpl<SurveyColumnMapper, SurveyColumn>
        implements SurveyColumnService {

    private @NonNull SurveyColumnMapper surveyColumnMapper;
    private @NonNull SurveyRespondentMapper surveyRespondentMapper;
    private @NonNull SurveyFilterMapper surveyFilterMapper;
    private @NonNull SurveyRespondentWeixinMapper surveyRespondentWeixinMapper;
    private @NonNull SurveyRespondentDataMapper surveyRespondentDataMapper;
    private @NonNull SurveyRespondentAttachmentMapper surveyRespondentAttachmentMapper;
    private @NonNull SurveyRespondentModifyLogMapper surveyRespondentModifyLogMapper;
    private @NonNull SurveyRespondentQuotaCheckItemMapper quotaCheckItemMapper;
    private @NonNull SurveyQuotaCheckItemMapper surveyQuotaCheckItemMapper;
    private @NonNull SurveyQuotaConditionMapper surveyQuotaConditionMapper;
    private @NonNull SurveyRespondentPropDataMapper surveyRespondentPropDataMapper;

    private @NonNull QdesGrpcClient qdesGrpcClient;
    private @NonNull MemberGrpcClient memberGrpcClient;
    private @NonNull UserGrpcClient userGrpcClient;
    private @NonNull DeliverGrpcClient deliverGrpcClient;

    private @NonNull SurveyCommonService surveyCommonService;
    private @NonNull SurveyQuotaConditionService surveyQuotaConditionService;
    private @NonNull SurveyMessageSendService surveyMessageSendService;
    private @NonNull SurveyRespondentService surveyRespondentService;

    private @NonNull RedisService redisService;

    @Resource(name = "saveRspdPropTaskExecutor")
    private ThreadPoolTaskExecutor saveRspdPropTaskExecutor;

    @Resource(name = "saveQuotaNumTaskExecutor")
    private ThreadPoolTaskExecutor saveQuotaNumTaskExecutor;

    private final ConcurrentHashMap<String, Function<JSONObject, Object>> QUESTION_PARSERS = new ConcurrentHashMap<String, Function<JSONObject, Object>>() {
        {
            put("blank", this::parseBlank);
            put("multiple_blank", this::parseMultiBlank);
            put("single", this::parseSingle);
            put("multiple", this::parseMultiple);
            put("score", this::parseScore);
            put("proportion", this::parseProportion);
            put("sort", this::parseSort);
            put("city", this::parseCity);
            put("matrix_single", this::parseMatrixSingle);
            put("matrix_multiple", this::parseMatrixMultiple);
            put("q_cascade", this::parseSingle);
            put("q_cascade_blank", this::parseSingle);
            put("matrix_blank", this::parseMatrixBlank);
            put("matrix_score", this::parseMatrixScore);
            put("cascader", this::parseCascader);
            put("timestamp", this::parseTimestamp);
            put("timedelta", this::parseTimedelta);
            put("evaluation", this::parseEvaluation);
            put("geo", this::parseGeo);
            put("auto_table", this::parseAutoTable);
        }

        private JSONArray parseBlank(JSONObject param) {
            param.getJSONObject("data").put("qtype", "blank");
            Object answerObj = param.get("answer");
            if (answerObj instanceof Map) {
                JSONArray jsonArray = JSONArray.parseArray(String.valueOf(((Map<?, ?>) answerObj).keySet()));
                param.put("answer", jsonArray);
                return parseBlank(param);
            }

            JSONArray answers = new JSONArray();
            JSONArray answer = (JSONArray) answerObj;
            if (ObjectUtil.isEmpty(answer)) {
                answers.add(new JSONObject());
            }
            for (Object ans : answer) {
                JSONObject jsonObject = new JSONObject();
                jsonObject.put("blank", ans);
                answers.add(jsonObject);
            }
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                param.getJSONObject("data").put("answers", answers.get(0));
            }
            return answers;
        }

        private JSONArray parseMultiBlank(JSONObject param) {
            JSONArray answers = new JSONArray();
            param.getJSONObject("data").put("qtype", "multiple_blank");
            for (Object object : param.getJSONObject("data").getJSONArray("items")) {
                JSONObject item = (JSONObject) object;
                JSONObject answer = param.getJSONObject("answer");
                JSONArray one_answer = answer.getJSONArray(item.getString("seq"));
                if (ObjectUtil.isEmpty(one_answer)) {
                    one_answer = answer.getJSONArray(item.getString("gid"));
                }
                JSONObject ans = new JSONObject();
                if (ObjectUtil.isNotEmpty(one_answer)) {
                    ans.put("gid", item.getString("gid"));
                    ans.put("seq", item.getString("seq"));
                    ans.put("title", item.getString("title"));
                    ans.put("blank", one_answer.get(0));
                }
                if (one_answer.size() > 1 && ObjectUtil.isNotEmpty(one_answer.get(1))) {
                    ans.put("open", one_answer.get(1));
                }
                answers.add(ans);
            }
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                param.getJSONObject("data").put("answers", answers);
            }
            return answers;
        }

        private JSONArray parseSingle(JSONObject param) {
            JSONObject options = new JSONObject();
            for (Object object : param.getJSONObject("data").getJSONArray("items")) {
                JSONObject option = (JSONObject) object;
                options.put(option.getString("gid"), option);
                options.put(option.getString("seq"), option);
            }

            JSONArray answers = new JSONArray();
            param.getJSONObject("data").put("qtype", "single");
            String custom_qtype = param.getJSONObject("data").getString("custom_qtype");
            Object answer = param.get("answer");
            for (Object seq : getOptions(answer)) {
                JSONObject ans = new JSONObject();
                JSONObject option = options.getJSONObject(String.valueOf(seq));
                if (ObjectUtil.isNotEmpty(option)) {
                    ans.put("gid", option.getString("gid"));
                    ans.put("seq", seq);
                    ans.put("title", option.getString("title"));
                    if (param.getBoolean("attachment_getter")) {
                        JSONObject jsonParam = new JSONObject();
                        jsonParam.putAll(param);
                        jsonParam.put("optionId", option.getString("gid"));
                        jsonParam.put("gid", param.getJSONObject("data").getString("gid"));
                        ans.put("attachments", getAttachments(jsonParam));
                    }
                    if ("image_select".equals(custom_qtype)) {
                        ans.put("src", option.get("src"));
                    }
                    if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                        //python是option.put
                        ans.put("is_selected", true);
                        option.put("is_selected", true);
                    }
                    if (((JSONArray) answer).size() > 1) {
                        if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                            option.put("open_answer", ((JSONArray) answer).get(1));
                        }
                        ans.put("open", ((JSONArray) answer).get(1));
                    }
                    answers.add(ans);
                } else {
                    answers.add(seq);
                }
            }
            return answers;
        }

        private JSONArray parseMultiple(JSONObject param) {
            JSONObject options = new JSONObject();
            for (Object object : param.getJSONObject("data").getJSONArray("items")) {
                JSONObject option = (JSONObject) object;
                options.put(option.getString("gid"), option);
                options.put(option.getString("seq"), option);
            }

            JSONArray answers = new JSONArray();
            param.getJSONObject("data").put("qtype", "multiple");
            String custom_qtype = param.getJSONObject("data").getString("custom_qtype");
            List<JSONArray> answer = param.getJSONArray("answer").toJavaList(JSONArray.class);
            //System.out.println(answer);
            //*parts [[61,11,11],[71,22]]
            if (ObjectUtil.isNotEmpty(answer)) {
                for (JSONArray temp : answer) {
                    JSONObject ans = new JSONObject();
                    String seq = temp.getString(0);
                    JSONObject option = options.getJSONObject(String.valueOf(seq));
                    if (ObjectUtil.isNotEmpty(option)) {
                        ans.put("gid", option.getString("gid"));
                        ans.put("seq", seq);
                        ans.put("title", option.getString("title"));
                        if (param.getBoolean("attachment_getter")) {
                            JSONObject jsonParam = new JSONObject();
                            jsonParam.putAll(param);
                            jsonParam.put("optionId", option.getString("gid"));
                            jsonParam.put("gid", param.getJSONObject("data").getString("gid"));
                            ans.put("attachments", getAttachments(jsonParam));
                        }
                        if ("image_select".equals(custom_qtype)) {
                            ans.put("src", option.get("src"));
                        }
                        boolean wholeQinfoFlag = Objects.equals(true, param.getBoolean("whole_qinfo"));
                        if (wholeQinfoFlag) {
                            //python是option.put
                            ans.put("is_selected", true);
                            option.put("is_selected", true);
                        }
                        List data = temp.subList(1, temp.size());
                        if (CollUtil.isNotEmpty(data)) {
                            if (wholeQinfoFlag) {
                                option.put("open_answer", data.get(0));
                            }
                            ans.put("open", data.get(0));
                        }
//                    if parts:
//                       if whole_qinfo:
//                          option['open_answer'] = parts[0]
//                       ans['open'] = parts[0]
                        answers.add(ans);
                    } else {
                        answers.add(ans);
                    }
                }
            }
            return answers;
        }

        private JSONArray parseScore(JSONObject param) {
            JSONArray answers = new JSONArray();
            param.getJSONObject("data").put("qtype", "score");
            String not_apply_text = "";
            boolean score_apply_type = Optional.ofNullable(param.getJSONObject("data").getBoolean("not_apply")).orElse(Boolean.FALSE);
            for (Object object : param.getJSONObject("data").getJSONArray("items")) {
                JSONObject ans = new JSONObject();
                ArrayList<Object> other = new ArrayList<>();
                JSONObject option = (JSONObject) object;
                JSONObject answer = param.getJSONObject("answer");
                Object one_answer = answer.get(option.getString("seq"));
                if (ObjectUtil.isEmpty(one_answer)) {
                    one_answer = answer.get(option.getString("gid"));
                }
                if (one_answer instanceof List) {
                    if (ObjectUtil.isNotEmpty(one_answer)) {
                        other.addAll(((List<?>) one_answer).subList(1, ((List<?>) one_answer).size()));
                        one_answer = ((List<?>) one_answer).get(0);
                        if (score_apply_type && Objects.equals(one_answer, "")) {
                            one_answer = not_apply_text;
                        }
                    } else {
                        continue;
                    }
                }
                if (one_answer instanceof String) {
                    if ((!NumberUtil.isNumber(String.valueOf(one_answer))) && !score_apply_type) {
                        continue;
                    }
                    if (ObjectUtil.isNotEmpty(one_answer) && !score_apply_type) {
                        one_answer = Integer.parseInt((String) one_answer);
                    }
                }
                ans.put("gid", option.getString("gid"));
                ans.put("seq", option.getString("seq"));
                ans.put("title", option.getString("title"));
                ans.put("score", ObjectUtil.isEmpty(one_answer) ? "" : one_answer);
                if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                    option.put("answer", one_answer);
                }
                if (!other.isEmpty()) {
                    if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                        option.put("open_answer", other.get(0));
                    }
                    ans.put("open", other.get(0));
                }
                if (other.size() > 1) {
                    if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                        option.put("reason_answer", other.get(1));
                    }
                    ans.put("reason", other.get(1));
                }
                answers.add(ans);
            }
            return answers;
        }

        private JSONArray parseProportion(JSONObject param) {
            JSONArray answers = new JSONArray();
            param.getJSONObject("data").put("qtype", "proportion");
            for (JSONObject option : param.getJSONObject("data").getJSONArray("items").toJavaList(JSONObject.class)) {
                JSONObject ans = new JSONObject();
                JSONObject answer = param.getJSONObject("answer");
                Object one_answer = answer.get(option.getString("seq"));
                if (Objects.isNull(one_answer)) {
                    one_answer = answer.get(option.getString("gid"));
                }
                List<Object> other = new ArrayList<>();
                if (one_answer instanceof List) {
                    List<?> one_answer_list = (List<?>) one_answer;
                    if (CollUtil.isNotEmpty(one_answer_list)) {
                        one_answer = one_answer_list.get(0);
                        if (one_answer_list.size() > 1) {
                            other.addAll(one_answer_list.subList(1, one_answer_list.size() - 1));
                        }
                    } else {
                        continue;
                    }
                }
                if (one_answer instanceof String) {
                    if (!NumberUtil.isNumber(String.valueOf(one_answer))) {
                        continue;
                    } else {
                        one_answer = Integer.parseInt((String) one_answer);
                    }
                }
                ans.put("gid", option.getString("gid"));
                ans.put("seq", option.getString("seq"));
                ans.put("title", option.getString("title"));
                ans.put("score", one_answer);
                boolean whole_qinfo = Objects.equals(true, param.getBoolean("whole_qinfo"));
                if (whole_qinfo) {
                    option.put("answer", one_answer);
                }
                if (!other.isEmpty()) {
                    if (whole_qinfo) {
                        option.put("open_answer", other.get(0));
                    }
                    ans.put("open", other.get(0));
                }
                if (other.size() > 1) {
                    if (whole_qinfo) {
                        option.put("reason_answer", other.get(1));
                    }
                    ans.put("reason", other.get(1));
                }
                answers.add(ans);
            }
            return answers;
        }

        private JSONArray parseSort(JSONObject param) {
            JSONArray answers = new JSONArray();
            param.getJSONObject("data").put("qtype", "sort");
            for (Object object : param.getJSONObject("data").getJSONArray("items")) {
                JSONObject item = (JSONObject) object;
                String seq = item.getString("seq");
                JSONObject answer = param.getJSONObject("answer");
                Object sort_num = answer.get(seq);
                if (ObjectUtil.isEmpty(sort_num)) {
                    sort_num = answer.get(item.getString("gid"));
                }
                JSONObject ans = new JSONObject();
                if (ObjectUtil.isNotEmpty(sort_num)) {
                    if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                        item.put("answer", sort_num);
                    }
                    ans.put("gid", item.getString("gid"));
                    ans.put("seq", item.getString("seq"));
                    ans.put("title", item.getString("title"));
                    ans.put("sort", sort_num);
                }
                answers.add(ans);
            }
            return answers;
        }

        private JSONArray parseCity(JSONObject param) {
            JSONArray answers = new JSONArray();
            param.getJSONObject("data").put("qtype", "city");
            Integer info_level = param.getJSONObject("data").getInteger("info_level");
            JSONObject answerObj = param.getJSONObject("answer");
            for (int i = 1; i < info_level + 1; i++) {
                answers.add(answerObj.get(String.valueOf(i)));
            }
            return answers;
        }

        private JSONArray parseMatrixSingle(JSONObject param) {
            JSONObject data = param.getJSONObject("data");
            data.put("qtype", "matrix_single");
            JSONArray answers = new JSONArray();
            JSONObject options = new JSONObject();
            JSONObject option = new JSONObject();
            //兼容旧问卷格式，gid和seq不相等的情况, 但优先考虑seq
            for (Object object : data.getJSONArray("items")) {
                option = (JSONObject) object;
                options.put(option.getString("gid"), option);
                options.put(option.getString("seq"), option);
            }
            List<JSONObject> row_items = data.getJSONArray("rows_items").toJavaList(JSONObject.class);
            boolean whole_qinfo = Objects.equals(true, param.getBoolean("whole_qinfo"));
            if (whole_qinfo) {
                JSONArray temp = new JSONArray();
                for (int i = 0; i < options.keySet().size(); i++) {
                    row_items.forEach(a -> temp.add(new JSONObject()));
                    option.put("answers", temp);
                }
            }
            for (JSONObject row_item : row_items) {
                JSONObject row_ans = new JSONObject();
                String seq = row_item.getString("seq");
                row_ans.put("gid", row_item.getString("gid"));
                row_ans.put("seq", seq);
                row_ans.put("title", row_item.getString("title"));
                //兼容旧问卷格式，gid和seq不相等的情况, 但优先考虑seq
                JSONObject answer = param.getJSONObject("answer");
                JSONArray row_answer = answer.getJSONArray(seq);
                if (CollUtil.isEmpty(row_answer)) {
                    row_answer = answer.getJSONArray(row_item.getString("gid"));
                }
                if (!row_answer.isEmpty() && options.containsKey(row_answer.getString(0))) {
                    option = options.getJSONObject(row_answer.getString(0));
                    JSONObject ans = new JSONObject();
                    JSONObject jsonObject = new JSONObject();
                    ans.put("gid", option.getString("gid"));
                    ans.put("seq", option.getString("seq"));
                    ans.put("title", option.getString("title"));
                    if (whole_qinfo) {
                        jsonObject.put("is_selected", true);
                        option.getJSONArray("answers").add(jsonObject);
                    }
                    if (row_answer.size() > 1 && CollUtil.isNotEmpty((JSONObject) row_answer.get(1))) {
                        ans.put("open", row_answer.get(1));
                        if (whole_qinfo) {
                            jsonObject.put("open", row_answer.get(1));
                            option.getJSONArray("answers").add(jsonObject);
                        }
                    }
                    row_ans.put("answer", Collections.singletonList(ans));
                    if (ObjectUtil.isNotEmpty(answer.get(seq + "_open"))) {
                        row_ans.put("open", answer.get(seq + "_open"));
                    }
                }
                answers.add(row_ans);
            }
            return answers;
        }

        private JSONArray parseMatrixMultiple(JSONObject param) {
            param.getJSONObject("data").put("qtype", "matrix_multiple");
            JSONArray answers = new JSONArray();
            JSONObject options = new JSONObject();
            JSONObject option = new JSONObject();
            for (Object object : param.getJSONObject("data").getJSONArray("items")) {
                option = (JSONObject) object;
                options.put(option.getString("gid"), option);
                options.put(option.getString("seq"), option);
            }
            JSONArray row_items = param.getJSONObject("data").getJSONArray("rows_items");
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                JSONArray temp = new JSONArray();
                for (int i = 0; i < options.keySet().size(); i++) {
                    row_items.forEach(a -> temp.add(new JSONObject()));
                    option.put("answers", temp);
                }
            }
            for (Object row_item : row_items) {
                JSONObject row_ans = new JSONObject();
                JSONObject rowItemsMap = (JSONObject) row_item;
                String seq = rowItemsMap.getString("seq");
                row_ans.put("gid", rowItemsMap.getString("gid"));
                row_ans.put("seq", seq);
                row_ans.put("title", rowItemsMap.getString("title"));
                JSONArray row_answer = param.getJSONObject("answer").getJSONArray(seq);
                if (ObjectUtil.isEmpty(row_answer)) {
                    row_answer = param.getJSONObject("answer").getJSONArray(rowItemsMap.getString("gid"));
                }
                if (!row_answer.isEmpty()) {
                    JSONArray row_ans_options = new JSONArray();
                    for (Object one_answer : row_answer) {
                        if (ObjectUtil.isNotEmpty(one_answer) && one_answer instanceof List) {
                            String op_seq = String.valueOf(((List<?>) one_answer).get(0));
                        } else if (one_answer instanceof Integer) {
                            String op_seq = one_answer.toString();
                            List<Object> temp = new ArrayList<>();
                            temp.add(op_seq);
                            one_answer = temp;
                        }
                        if (!((JSONArray) one_answer).isEmpty() && options.containsKey(((JSONArray) one_answer).getString(0))) {
                            option = options.getJSONObject(((JSONArray) one_answer).getString(0));
                            JSONObject ans = new JSONObject();
                            JSONObject jsonObject = new JSONObject();
                            ans.put("gid", option.getString("gid"));
                            ans.put("seq", option.getString("seq"));
                            ans.put("title", option.getString("title"));
                            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                                jsonObject.put("is_selected", true);
                                option.getJSONArray("answers").add(jsonObject);
                            }
                            if (row_answer.size() > 1) {
                                ans.put("open", row_answer.get(1));
                                if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                                    jsonObject.put("open", row_answer.get(1));
                                    option.getJSONArray("answers").add(jsonObject);
                                }
                            }
                            row_ans_options.add(ans);
                        }
                    }
                    row_ans.put("answer", row_ans_options);
                    if (ObjectUtil.isNotEmpty(param.getJSONObject("answer").get(seq + "_open"))) {
                        row_ans.put("open", param.getJSONObject("answer").get(seq + "_open"));
                    }
                }
                answers.add(row_ans);
            }
            return answers;
        }

        private JSONArray parseMatrixBlank(JSONObject param) {
            param.getJSONObject("data").put("qtype", "matrix_blank");
            JSONArray answers = new JSONArray();
            JSONObject options = new JSONObject();
            JSONObject option = new JSONObject();
            //兼容旧问卷格式，gid和seq不相等的情况, 但优先考虑seq
            for (Object object : param.getJSONObject("data").getJSONArray("items")) {
                option = (JSONObject) object;
                options.put(option.getString("gid"), option);
                options.put(option.getString("seq"), option);
            }
            JSONArray row_items = param.getJSONObject("data").getJSONArray("rows_items");
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                JSONArray temp = new JSONArray();
                for (int i = 0; i < options.keySet().size(); i++) {
                    row_items.forEach(a -> temp.add(new JSONObject()));
                    option.put("answers", temp);
                }
            }
            JSONObject answer = param.getJSONObject("answer");
            for (Object row_item : row_items) {
                JSONObject row_ans = new JSONObject();
                JSONObject rowItemsMap = (JSONObject) row_item;
                List<Object> other = new ArrayList<>();
                row_ans.put("gid", rowItemsMap.getString("gid"));
                row_ans.put("title", rowItemsMap.getString("title"));
                JSONObject row_answer = answer.getJSONObject(rowItemsMap.getString("seq"));
                if (ObjectUtil.isEmpty(row_answer)) {
                    row_answer = answer.getJSONObject(rowItemsMap.getString("gid"));
                }
                if (!row_answer.isEmpty()) {
                    JSONArray row_ans_options = new JSONArray();
                    row_answer.forEach((seq, one_answer) -> {
                        JSONObject o = options.getJSONObject(seq);
                        if (one_answer instanceof List) {
                            if (((List<?>) one_answer).size() > 1) {
                                other.addAll(((List<?>) one_answer).subList(1, ((List<?>) one_answer).size()));
                            }
                            one_answer = ((List<?>) one_answer).get(0);
                        }
                        if (ObjectUtil.isNotEmpty(o)) {
                            JSONObject op_ans = new JSONObject();
                            op_ans.put("gid", o.getString("gid"));
                            op_ans.put("seq", o.getString("seq"));
                            op_ans.put("title", o.getString("title"));
                            op_ans.put("blank", one_answer);
                            if (!other.isEmpty()) {
                                op_ans.put("open", other.get(0));
                            }
//                            if whole_qinfo:
//                            option['answers'][row_idx]['blank'] = one_answer
//                            if other:
//                            option['answers'][row_idx]['open'] = other[0]
                            row_ans_options.add(op_ans);
                        }
                    });
                    row_ans.put("answer", row_ans_options);
                    Object row_open = answer.get(rowItemsMap.getString("gid") + "_open");
                    if (Objects.isNull(row_open)) {
                        row_open = answer.get(rowItemsMap.getString("seq") + "_open");
                    }
                    if (ObjectUtil.isEmpty(row_open)) {
                        row_ans.put("open", row_open);
                    }
                    answers.add(row_ans);
                }
            }
            return answers;
        }

        private JSONArray parseMatrixScore(JSONObject param) {
            param.getJSONObject("data").put("qtype", "matrix_score");
            JSONArray answers = new JSONArray();
            JSONObject options = new JSONObject();
            JSONObject option = new JSONObject();
            //兼容旧问卷格式，gid和seq不相等的情况, 但优先考虑seq
            for (Object object : param.getJSONObject("data").getJSONArray("items")) {
                option = (JSONObject) object;
                options.put(option.getString("gid"), option);
                options.put(option.getString("seq"), option);
            }
            JSONArray row_items = param.getJSONObject("data").getJSONArray("rows_items");
            boolean score_apply_type = Optional.ofNullable(param.getJSONObject("data").getBoolean("not_apply")).orElse(Boolean.FALSE);
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                JSONArray temp = new JSONArray();
                for (int i = 0; i < options.keySet().size(); i++) {
                    row_items.forEach(a -> temp.add(new JSONObject()));
                    option.put("answers", temp);
                }
            }
            for (Object row_item : row_items) {
                JSONObject row_ans = new JSONObject();
                JSONObject rowItemsMap = (JSONObject) row_item;
                List<Object> other = new ArrayList<>();
                row_ans.put("gid", rowItemsMap.getString("gid"));
                row_ans.put("title", rowItemsMap.getString("title"));
                JSONObject row_answer = param.getJSONObject("answer").getJSONObject(rowItemsMap.getString("seq"));
                if (ObjectUtil.isEmpty(row_answer)) {
                    row_answer = param.getJSONObject("answer").getJSONObject(rowItemsMap.getString("gid"));
                }
                if (!row_answer.isEmpty()) {
                    JSONArray row_ans_options = new JSONArray();
                    row_answer.forEach((seq, one_answer) -> {
                        JSONObject o;
                        if (seq.contains("no_apply")) {
                            o = options.getJSONObject(seq);
                        } else {
                            o = options.getJSONObject(seq.split("-")[0]);
                        }
                        if (one_answer instanceof List) {
                            if (((List<?>) one_answer).size() > 1) {
                                other.addAll(((List<?>) one_answer).subList(1, ((List<?>) one_answer).size()));
                            }
                            one_answer = ((List<?>) one_answer).get(0);
                        }
                        boolean is_no_apply = false;
                        if (score_apply_type && (ObjectUtil.isEmpty(one_answer) || Objects.equals(99, one_answer))) {
                            one_answer = "";
                            is_no_apply = true;
                        }
                        if (ObjectUtil.isNotEmpty(o)) {
                            JSONObject op_ans = new JSONObject();
                            op_ans.put("gid", o.getString("gid"));
                            op_ans.put("seq", !is_no_apply ? o.getString("seq") : seq);
                            op_ans.put("title", o.getString("title"));
                            op_ans.put("score", one_answer);
                            if (!other.isEmpty()) {
                                op_ans.put("open", other.get(0));
                            }
//                            if whole_qinfo:
//                            option['answers'][row_idx]['blank'] = one_answer
//                            if other:
//                            option['answers'][row_idx]['open'] = other[0]
                            row_ans_options.add(op_ans);
                        }
                    });
                    row_ans.put("answer", row_ans_options);
                    Object row_open = param.getJSONObject("answer").get(rowItemsMap.getString("seq") + "_open");
                    if (Objects.isNull(row_open)) {
                        row_open = param.getJSONObject("answer").get(rowItemsMap.getString("gid") + "_open");
                    }
                    if (ObjectUtil.isNotEmpty(row_open)) {
                        row_ans.put("open", row_open);
                    }
                    answers.add(row_ans);
                }
            }
            return answers;
        }

        private JSONArray parseCascader(JSONObject param) {
            JSONObject data = param.getJSONObject("data");
            data.put("qtype", "cascader");
            JSONArray answers = new JSONArray();
            JSONObject total_option_dict = new JSONObject();
            for (Object object : data.getJSONArray("option_list")) {
                JSONObject option = (JSONObject) object;
                total_option_dict.put(option.getString("gid"), option);
            }
            JSONArray group_list = data.getJSONArray("group_list");
            JSONObject answerObj = param.getJSONObject("answer");
            for (int i = 1; i < group_list.size() + 1; i++) {
                JSONArray seqs = answerObj.getJSONArray(String.valueOf(i));
                if (ObjectUtil.isEmpty(seqs)) {
                    break;
                }
                String seq = seqs.getString(0);
                JSONObject option = total_option_dict.getJSONObject(seq);
                if (ObjectUtil.isEmpty(option)) {
                    break;
                }
                JSONObject temp = new JSONObject();
                temp.put("gid", option.getString("gid"));
                temp.put("seq", option.getString("seq"));
                temp.put("title", option.getString("title"));
                answers.add(temp);
            }
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                data.put("answers", answers);
            }
            return answers;
        }

        private JSONObject parseTimestamp(JSONObject param) {
            param.getJSONObject("data").put("qtype", "timestamp");
            JSONObject answers = new JSONObject();
            Object answer = param.get("answer");
            if (answer instanceof List) {
                if (ObjectUtil.isNotEmpty(answer)) {
                    answers.put("ts", ((List<?>) answer).get(0));
                }
                if (((List<?>) answer).size() > 1) {
                    answers.put("tz", ((List<?>) answer).get(1));
                }
            } else if (answer instanceof Integer) {
                answers.put("ts", answer);
            }
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                param.getJSONObject("data").put("answers", answers);
            }
            return answers;
        }

        private Integer parseTimedelta(JSONObject param) {
            JSONObject data = param.getJSONObject("data");
            data.put("qtype", "timedelta");
            Integer answers = param.getJSONArray("answer").getInteger(0);
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                data.put("answers", answers);
            }
            return answers;
        }

        private JSONObject parseEvaluation(JSONObject param) {
            param.getJSONObject("data").put("qtype", "evaluation");
            boolean score_apply_type = Optional.ofNullable(param.getJSONObject("data").getBoolean("not_apply")).orElse(Boolean.FALSE);
            JSONObject answer = param.getJSONObject("answer");
            if (score_apply_type && ObjectUtil.isEmpty(answer.get("score"))) {
                answer.put("score", "");
            }
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                param.getJSONObject("data").put("answers", answer);
            }
            return answer;
        }

        private Object parseGeo(JSONObject param) {
            param.getJSONObject("data").put("qtype", "geo");
            Object answer = param.get("answer");
            if (answer instanceof List) {
                JSONObject temp = new JSONObject();
                temp.put("coordinate", param.get("answer"));
                temp.put("city", new JSONObject());
                param.put("answer", temp);
            }
            if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                param.getJSONObject("data").put("answers", answer);
            }
            return answer;
        }

        private JSONArray parseAutoTable(JSONObject param) {
            JSONArray answers = new JSONArray();
            param.getJSONObject("data").put("qtype", "auto_table");
            for (Object object : param.getJSONObject("data").getJSONArray("items")) {
                JSONObject op_answers = new JSONObject();
                JSONObject option = (JSONObject) object;
                String option_id = option.getString("gid");
                op_answers.put("gid", option_id);
                op_answers.put("title", option.getString("title"));
                JSONArray _answers = new JSONArray();
                JSONArray answer = param.getJSONArray("answer");
                for (Object ans : answer) {
                    _answers.add(((JSONObject) ans).get(option_id));
                }
                op_answers.put("answer", _answers);
                answers.add(op_answers);
                if (Objects.equals(true, param.getBoolean("whole_qinfo"))) {
                    option.put("answers", _answers);
                }
            }
            return answers;
        }
    };

    @Override
    public JSONObject putSurveyColumns(String surveyColumnId, String projectId, JSONObject param) {
        if (StrUtil.isNotEmpty(surveyColumnId)) {
            param.put("id", surveyColumnId);
            Optional.ofNullable(surveyColumnMapper.selectById(surveyColumnId)).orElseThrow(() -> new BusinessException("Column[id=" + surveyColumnId + "] not found"));
        }

        Integer status = param.getInteger("status");
        if (ObjectUtil.isEmpty(status)) {
            throw new BusinessException("status not found");
        }
        if (param.containsKey("idList")) {
            JSONArray idList = param.getJSONArray("idList");
            if (status == COLUMN_STATUS_SHOW.getNumber()) {
                LambdaUpdateWrapper<SurveyColumn> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.set(SurveyColumn::getStatus, status)
                        .eq(SurveyColumn::getProjectId, projectId)
                        .in(SurveyColumn::getId, idList)
                        .ne(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber());
                surveyColumnMapper.update(null, updateWrapper);
                LambdaUpdateWrapper<SurveyColumn> updateWrapper1 = new LambdaUpdateWrapper<>();
                updateWrapper1.set(SurveyColumn::getStatus, COLUMN_STATUS_HIDE.getNumber())
                        .eq(SurveyColumn::getProjectId, projectId)
                        .notIn(SurveyColumn::getId, idList)
                        .ne(SurveyColumn::getStatus, COLUMN_STATUS_DELETE.getNumber());
                surveyColumnMapper.update(null, updateWrapper1);
            } else {
                LambdaUpdateWrapper<SurveyColumn> updateWrapper3 = new LambdaUpdateWrapper<>();
                updateWrapper3.set(SurveyColumn::getStatus, status)
                        .eq(SurveyColumn::getProjectId, projectId)
                        .in(SurveyColumn::getId, idList);
                surveyColumnMapper.update(null, updateWrapper3);
            }
            JSONObject resultJson = new JSONObject();
            resultJson.put("ids", idList);
            return resultJson;
        } else {
            SurveyColumn update = param.toJavaObject(SurveyColumn.class);
            surveyColumnMapper.updateById(update);
            //LambdaUpdateWrapper<SurveyColumn> updateWrap = new UpdateWrapper<>(update).lambda();
            //surveyColumnMapper.update(null, updateWrap);
            JSONObject resultJson = new JSONObject();
            resultJson.put("id", surveyColumnId);
            return resultJson;
        }
    }

    @Override
    public Map<String, Integer> putResume(String projectId, String seq, JSONObject param) {
        Project project = qdesGrpcClient.getProject(projectId);
        if (ObjectUtil.isEmpty(project)) {
            throw new BusinessException("Project[id={" + projectId + "}] not found");
        }
        if (!SecurityContextHolder.getOrgId().equals(project.getCompanyId())) {
            // 具有权限检查
            throw new BusinessException("no permission");
        }
        if (ObjectUtil.isNotEmpty(seq)) {
            return resumeRespondent(projectId, seq, null);
        }
        List<Object> seqList = param.getJSONArray("seqList");
        if (ObjectUtil.isNotEmpty(seqList)) {
            return resumeRespondent(projectId, null, seqList);
        }

        //这个todo是永远走不到得逻辑，暂不修复
        //TODO seq 和 seqList都不传的情况下 params = await self.get_filter(pid)

        return null;
    }

    @Override
    public Map<String, Integer> putDiscard(String projectId, String seq, JSONObject param) throws Exception {
        Project project = qdesGrpcClient.getProject(projectId);
        if (ObjectUtil.isEmpty(project)) {
            throw new BusinessException("Project[id={" + projectId + "}] not found");
        }
        if (!SecurityContextHolder.getOrgId().equals(project.getCompanyId())) {
            // 具有权限检查
            throw new BusinessException("no permission");
        }
        if (ObjectUtil.isNotEmpty(seq)) {
            return discardRespondent(projectId, seq, null);
        }
        List<Object> seqList = param.getJSONArray("seqList");
        if (ObjectUtil.isNotEmpty(seqList)) {
            return discardRespondent(projectId, null, seqList);
        }

        //这个todo是永远走不到得逻辑，暂不修复
        //TODO seq 和 seqList都不传的情况下 params = await self.get_filter(pid)
        return null;
    }

    @Override
    public SurveyFilter getFilter(String projectId) {
        LambdaQueryWrapper<SurveyFilter> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(SurveyFilter::getProjectId, projectId);
        return surveyFilterMapper.selectOne(queryWrapper);
    }

    @Override
    public void putFilter(String projectId, JSONObject args) {
        List<String> fieldList = Lists.newArrayList("status", "sourceList", "groupList", "qlist", "condList", "action", "qLogic", "condLogic", "ext");
        JSONObject params = new JSONObject();
        args.keySet().forEach(a -> {
            if (fieldList.contains(a)) {
                params.put(a, args.get(a));
            }
        });
        if (params.containsKey("ext") && ObjectUtil.isNotEmpty(args.getJSONObject("ext"))) {
            String orgId = SecurityContextHolder.getOrgId();
            for (int i = 0; i < args.getJSONObject("ext").getJSONArray("v").size(); i++) {
                JSONObject p = args.getJSONObject("ext").getJSONArray("v").getJSONObject(i);
                List<MemberDto> memberDtos = memberGrpcClient.ListMembersByParam(orgId, p.getInteger("mtype"), p.getString("dtype"));
                List<Object> tempList = new ArrayList<>();
                for (MemberDto memberDto : memberDtos) {
                    p.put("v", tempList.add(JSON.parseObject(JSONObject.toJSONString(memberDto)).get(p.getString("dtype"))));
                }
            }
        }
        SurveyFilter surveyFilter = params.toJavaObject(SurveyFilter.class);
        SurveyFilter filter = getFilter(projectId);
        if (Objects.isNull(filter)) {
            surveyFilter.setProjectId(projectId);
            surveyFilterMapper.insert(surveyFilter);
        } else {
            surveyFilter.setId(filter.getId());
            surveyFilterMapper.updateById(surveyFilter);
        }
        redisService.del(projectId);
    }

    @Override
    public JSONObject getRespondentsDetail(String seq, JSONObject args) {
        //整个方法传递的参数
        JSONObject param = new JSONObject();

        SurveyRespondent surveyRespondent = surveyRespondentMapper.selectOne(
                new QueryWrapper<SurveyRespondent>().lambda()
                        .eq(SurveyRespondent::getSeq, seq));
        if (ObjectUtil.isNull(surveyRespondent)) {
            throw new BusinessException("Respondent[seq={" + seq + "}] not found");
        }
        String projectId = surveyRespondent.getProjectId();
        SurveyRespondentWeixin wx_info = surveyRespondentWeixinMapper.selectOne(
                new QueryWrapper<SurveyRespondentWeixin>().lambda().eq(SurveyRespondentWeixin::getRid, surveyRespondent.getId()));
        surveyRespondent.setWxInfo(wx_info);
        JSONObject rspd = JSONObject.parseObject(JSON.toJSONString(surveyRespondent));
        Project project = qdesGrpcClient.getProject(projectId);
        if (ObjectUtil.isEmpty(project)) {
            throw new BusinessException("Project[id={" + projectId + "}] not found");
        }
        if (!SecurityContextHolder.getOrgId().equals(project.getCompanyId())) {
            // 具有权限检查
            throw new BusinessException("no permission");
        }
        //答卷详情、导出时填充答卷信息
        fillRspdinfo(rspd);
        //从答卷中获取得分
        param.put("id", surveyRespondent.getId());
        param.put("projectId", projectId);
        param.put("version", surveyRespondent.getVersion());
        param.put("seq", seq);
        param.put("status", surveyRespondent.getStatus());
        Double score = getScoreByAnswer(param);
        if (score != null) {
            rspd.put("score", score);
        }
        if (ObjectUtil.isNotEmpty(args.get("withProject"))) {
            rspd.put("project_title", project.getTitle());
            rspd.put("survey_title", getsurveytitle(projectId, project.getVersion()));
        }
        SurveyRespondentData rspd_data = getRespondentDataByRid(surveyRespondent.getId(), projectId);
        String userId = SecurityContextHolder.getUserId();
        JSONObject sensitive_map = getUidsSensitives(projectId, userId);
        Integer version;
        if (ObjectUtil.isNotEmpty(args.get("by_version"))) {
            //答卷版本返回对应的答卷详情
            version = rspd.getInteger("version");
        } else {
            //最新版本返回对应的答卷详情
            version = project.getReleaseVer();
        }
        param.put("version", version);
        param.put("answers", rspd_data.getAnswers());
        param.put("whole_qinfo", false);
        param.put("with_attach", true);
        param.put("sensitive_map", sensitive_map);
        Object answers = parseAnswers(param, null);
        JSONArray ext = parseCustomAnswers(rspd_data.getCustomAnswers());
        JSONObject data = new JSONObject();
        data.put("rspd", rspd);
        data.put("answers", answers);
        data.put("ext", ext);
        //修改答卷设置的 getRespondentsAnswers
        if (args.getBooleanValue("updataFlay")) {
            data.put("one_answer", JSONObject.parseObject(rspd_data.getAnswers()).getString(args.getString("gid")));
        }
        JSONObject result = new JSONObject();
        result.put("data", data);
        return result;
    }

    @Override
    public List<SurveyRespondentAttachment> getAttachments(String projectId, String seq) {
        return surveyRespondentAttachmentMapper.selectList(new QueryWrapper<SurveyRespondentAttachment>().lambda()
                .eq(SurveyRespondentAttachment::getProjectId, projectId)
                .eq(SurveyRespondentAttachment::getSeq, seq));
    }

    private Map<String, Integer> resumeRespondent(String projectId, String seq, List<Object> seqList) {
        Map<String, Integer> map = new HashMap<>();
        if (ObjectUtil.isNotNull(seq)) {
            SurveyRespondent surveyRespondent = surveyRespondentMapper.selectOne(
                    new QueryWrapper<SurveyRespondent>().lambda()
                            .eq(SurveyRespondent::getProjectId, projectId)
                            .eq(SurveyRespondent::getStatus, RESPONDENT_STATUS_DISCARD.getNumber())
                            .eq(SurveyRespondent::getSeq, seq));
            if (ObjectUtil.isEmpty(surveyRespondent)) {
                throw new BusinessException("Respondent[project_id={" + projectId + "},seq={" + seq + "},status={" + RESPONDENT_STATUS_DISCARD.getNumber() + "}] not found");
            } else {
                LambdaUpdateWrapper<SurveyRespondent> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.set(SurveyRespondent::getUpdatedDt, LocalDateTime.now())
                        .set(SurveyRespondent::getPreDiscardStatus, null)
                        .eq(SurveyRespondent::getProjectId, projectId)
                        .eq(SurveyRespondent::getSeq, seq);
                if (ObjectUtil.equals(surveyRespondent.getPreDiscardStatus(), RESPONDENT_STATUS_BEGIN.getNumber())) {
                    updateWrapper.set(SurveyRespondent::getStatus, RESPONDENT_STATUS_UNFINISH.getNumber());
                } else {
                    updateWrapper.set(SurveyRespondent::getStatus, surveyRespondent.getPreDiscardStatus());
                }
                surveyRespondentMapper.update(null, updateWrapper);

                SurveyRespondent up_rspd = surveyRespondentMapper.selectOne(
                        new QueryWrapper<SurveyRespondent>().lambda()
                                .eq(SurveyRespondent::getProjectId, projectId)
                                .eq(SurveyRespondent::getSeq, seq));
                up_rspd.setStatus(surveyRespondent.getPreDiscardStatus());

                JSONObject data = new JSONObject();
                data.put("project_id", projectId);
                data.put("version", surveyRespondent.getVersion());
                data.put("seq", surveyRespondent.getSeq());
                data.put("time", LocalDateTimeUtil.formatNormal(surveyRespondent.getCreatedDt()));
                data.put("prev", RESPONDENT_STATUS_DISCARD.getNumber());
                data.put("current", surveyRespondent.getPreDiscardStatus());
                surveyMessageSendService.surveyRespondentStatusChangedSend(data);

                SurveyRespondentData respondent_data = surveyRespondentDataMapper.selectOne(
                        new LambdaQueryWrapper<SurveyRespondentData>()
                                .eq(SurveyRespondentData::getProjectId, surveyRespondent.getProjectId())
                                .eq(SurveyRespondentData::getSeq, surveyRespondent.getSeq())
                                .last("limit 1"));
                if (Objects.nonNull(respondent_data)) {
                    surveyCommonService.mqSurveyRespondentModify(up_rspd, JSONObject.parseObject(respondent_data.getAnswers()), JSONObject.parseObject(respondent_data.getCustomAnswers()));
                }

                List<Quota> quotas = qdesGrpcClient.getQuotaList(projectId, null);
                quotas.forEach(a -> CompletableFuture.runAsync(() -> surveyQuotaConditionService.genQuotaNumDelay(projectId, a.getId(), Long.valueOf(seq)), saveQuotaNumTaskExecutor));
                //# 同步更新Prop表数据
                CompletableFuture.runAsync(() -> surveyCommonService.saveRspdPropTaskWrapper(projectId, Integer.valueOf(seq), up_rspd, null, null, null, null, null), saveRspdPropTaskExecutor);
            }
            map.put("resumeCount", 1);
        }

        if (ObjectUtil.isNotNull(seqList)) {
            seqList.forEach(seqValue -> {
                LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(SurveyRespondent::getProjectId, projectId)
                        .eq(SurveyRespondent::getStatus, RESPONDENT_STATUS_DISCARD.getNumber())
                        .eq(SurveyRespondent::getSeq, seqValue);
                SurveyRespondent surveyRespondent = surveyRespondentMapper.selectOne(queryWrapper);
                if (ObjectUtil.isEmpty(surveyRespondent)) {
                    throw new BusinessException("Respondent[project_id={" + projectId + "},seq={" + seqValue + "},status={" + RESPONDENT_STATUS_DISCARD.getNumber() + "}] not found");
                } else {
                    LambdaUpdateWrapper<SurveyRespondent> updateWrapper = new LambdaUpdateWrapper<>();
                    updateWrapper.set(SurveyRespondent::getUpdatedDt, LocalDateTime.now())
                            .set(SurveyRespondent::getPreDiscardStatus, null)
                            .eq(SurveyRespondent::getProjectId, projectId)
                            .eq(SurveyRespondent::getSeq, seqValue);
                    if (ObjectUtil.equals(surveyRespondent.getPreDiscardStatus(), RESPONDENT_STATUS_BEGIN.getNumber())) {
                        updateWrapper.set(SurveyRespondent::getStatus, RESPONDENT_STATUS_UNFINISH.getNumber());
                    } else {
                        updateWrapper.set(SurveyRespondent::getStatus, surveyRespondent.getPreDiscardStatus());
                    }
                    surveyRespondentMapper.update(null, updateWrapper);

                    SurveyRespondent up_rspd = surveyRespondentMapper.selectOne(
                            new QueryWrapper<SurveyRespondent>().lambda()
                                    .eq(SurveyRespondent::getProjectId, projectId)
                                    .eq(SurveyRespondent::getSeq, seqValue));
                    up_rspd.setStatus(surveyRespondent.getPreDiscardStatus());

                    JSONObject data = new JSONObject();
                    data.put("project_id", projectId);
                    data.put("version", surveyRespondent.getVersion());
                    data.put("seq", surveyRespondent.getSeq());
                    data.put("time", LocalDateTimeUtil.formatNormal(surveyRespondent.getCreatedDt()));
                    data.put("prev", RESPONDENT_STATUS_DISCARD.getNumber());
                    data.put("current", surveyRespondent.getPreDiscardStatus());
                    surveyMessageSendService.surveyRespondentStatusChangedSend(data);

                    SurveyRespondentData respondent_data = surveyRespondentDataMapper.selectOne(
                            new LambdaQueryWrapper<SurveyRespondentData>()
                                    .eq(SurveyRespondentData::getProjectId, surveyRespondent.getProjectId())
                                    .eq(SurveyRespondentData::getSeq, surveyRespondent.getSeq())
                                    .last("limit 1"));
                    if (Objects.nonNull(respondent_data)) {
                        surveyCommonService.mqSurveyRespondentModify(up_rspd, JSONObject.parseObject(respondent_data.getAnswers()), JSONObject.parseObject(respondent_data.getCustomAnswers()));
                    }

                    List<Quota> quotas = qdesGrpcClient.getQuotaList(projectId, null);
                    quotas.forEach(a -> CompletableFuture.runAsync(() -> surveyQuotaConditionService.genQuotaNumDelay(projectId, a.getId(), (Long) seqValue), saveQuotaNumTaskExecutor));
                    //# 同步更新Prop表数据
                    CompletableFuture.runAsync(() -> surveyCommonService.saveRspdPropTaskWrapper(projectId, Integer.valueOf((String) seqValue), up_rspd, null, null, null, null, null), saveRspdPropTaskExecutor);
                }
            });
            map.put("resumeCount", seqList.size());
        }
        return map;
    }

    private Map<String, Integer> discardRespondent(String projectId, String seq, List<Object> seqList) throws Exception {
        Map<String, Integer> map = new HashMap<>();
        if (ObjectUtil.isNotNull(seq)) {
            SurveyRespondent surveyRespondent = surveyRespondentMapper.selectOne(
                    new QueryWrapper<SurveyRespondent>().lambda()
                            .eq(SurveyRespondent::getProjectId, projectId)
                            .ne(SurveyRespondent::getStatus, RESPONDENT_STATUS_DISCARD.getNumber())
                            .eq(SurveyRespondent::getSeq, seq));
            if (ObjectUtil.isEmpty(surveyRespondent)) {
                throw new BusinessException("Respondent[project_id={" + projectId + "},seq={" + seq + "},status!={" + RESPONDENT_STATUS_DISCARD.getNumber() + "}] not found");
            } else {
                Integer old_status = surveyRespondent.getStatus();
                //await trigger_rspd_status(rspd, 'discard', project_id=rspd.project_id)
                surveyCommonService.triggerRspdStatus(surveyRespondent, "discard", surveyRespondent.getProjectId());

                if (ObjectUtil.equals(old_status, RESPONDENT_STATUS_BEGIN.getNumber())) {
                    old_status = RESPONDENT_STATUS_UNFINISH.getNumber();
                }

                LambdaUpdateWrapper<SurveyRespondent> updateWrapper = new LambdaUpdateWrapper<>();
                updateWrapper.set(SurveyRespondent::getUpdatedDt, LocalDateTime.now())
                        .set(SurveyRespondent::getPreDiscardStatus, old_status)
                        .eq(SurveyRespondent::getProjectId, projectId)
                        .eq(SurveyRespondent::getSeq, seq);
                surveyRespondentMapper.update(null, updateWrapper);

                SurveyRespondent up_rspd = surveyRespondentMapper.selectOne(
                        new QueryWrapper<SurveyRespondent>().lambda()
                                .eq(SurveyRespondent::getProjectId, projectId)
                                .eq(SurveyRespondent::getSeq, seq));
                //同步更新Prop表数据
                CompletableFuture.runAsync(() -> surveyCommonService.saveRspdPropTaskWrapper(projectId, Integer.valueOf(seq), up_rspd, null, null, null, RESPONDENT_STATUS_DISCARD.getNumber(), null), saveRspdPropTaskExecutor);
                redisService.del("rspd_prop_" + projectId);
                JSONArray jsonArray = JSON.parseArray(up_rspd.getQuotaCheckitemList());
                if (ObjectUtil.isNotEmpty(jsonArray)) {
                    List<String> quotaCheckitemList = new ArrayList<>();
                    JSONArray quotaRespCheckitemList = new JSONArray();
                    for (int i = 0; i < jsonArray.size(); i++) {
                        quotaCheckitemList.add(jsonArray.getJSONObject(i).getString("quota_checkitem_id"));
                        quotaRespCheckitemList.add(jsonArray.getJSONObject(i).get("id"));
                    }

                    //发布删除配额消息
                    quotaCheckItemMapper.deleteBatchIds(quotaRespCheckitemList);
                    CreateQuotaDto mqdata = new CreateQuotaDto();
                    mqdata.setProjectId(projectId);
                    mqdata.setQuotaCheckitemIds(quotaCheckitemList);
                    surveyMessageSendService.surveyPopQuoteSend(mqdata);

                    List<String> quota_cond_ids = surveyQuotaCheckItemMapper.selectBatchIds(quotaCheckitemList)
                            .stream().map(SurveyQuotaCheckItem::getQuotaCondId).collect(Collectors.toList());
                    if (ObjectUtil.isNotEmpty(quota_cond_ids)) {
                        List<String> quota_id_list = surveyQuotaConditionMapper.selectBatchIds(quota_cond_ids)
                                .stream().map(SurveyQuotaCondition::getQuotaId).collect(Collectors.toList());
                        quota_id_list.stream().filter(StrUtil::isNotBlank).forEach(quota_id -> surveyQuotaConditionService.refreshQuotaProgress(quota_id));
                    }
                }
                JSONObject data = new JSONObject();
                data.put("seq", surveyRespondent.getSeq());
                data.put("deliver_group_id", surveyRespondent.getDeliverGroupId());
                data.put("deliver_id", surveyRespondent.getDeliverId());
                data.put("member_id", surveyRespondent.getMemberId());
                data.put("project_id", projectId);
                data.put("dlvr_rid", surveyRespondent.getDlvrRid());
                surveyMessageSendService.surveyRespondentUnfinishSend(data);
            }
            map.put("discardCount", 1);
        }

        if (ObjectUtil.isNotNull(seqList)) {
            seqList.forEach(seqValue -> {
                LambdaQueryWrapper<SurveyRespondent> queryWrapper = new LambdaQueryWrapper<>();
                queryWrapper.eq(SurveyRespondent::getProjectId, projectId)
                        .ne(SurveyRespondent::getStatus, RESPONDENT_STATUS_DISCARD.getNumber())
                        .eq(SurveyRespondent::getSeq, seqValue);
                SurveyRespondent surveyRespondent = surveyRespondentMapper.selectOne(queryWrapper);
                if (ObjectUtil.isEmpty(surveyRespondent)) {
                    throw new BusinessException("Respondent[project_id={" + projectId + "},seq={" + seqValue + "},status!={" + RESPONDENT_STATUS_DISCARD.getNumber() + "}] not found");
                } else {
                    Integer old_status = surveyRespondent.getStatus();
                    //await trigger_rspd_status(rspd, 'discard', project_id=rspd.project_id)
                    try {
                        surveyCommonService.triggerRspdStatus(surveyRespondent, "discard", surveyRespondent.getProjectId());
                    } catch (Exception e) {
                        log.error("triggerRspdStatus error");
                        throw new BusinessException("triggerRspdStatus error");
                    }

                    if (ObjectUtil.equals(old_status, RESPONDENT_STATUS_BEGIN.getNumber())) {
                        old_status = RESPONDENT_STATUS_UNFINISH.getNumber();
                    }

                    LambdaUpdateWrapper<SurveyRespondent> updateWrapper = new LambdaUpdateWrapper<>();
                    updateWrapper.set(SurveyRespondent::getUpdatedDt, LocalDateTime.now())
                            .set(SurveyRespondent::getPreDiscardStatus, old_status)
                            .eq(SurveyRespondent::getProjectId, projectId)
                            .eq(SurveyRespondent::getSeq, seqValue);
                    surveyRespondentMapper.update(null, updateWrapper);

                    SurveyRespondent up_rspd = surveyRespondentMapper.selectOne(
                            new QueryWrapper<SurveyRespondent>().lambda()
                                    .eq(SurveyRespondent::getProjectId, projectId)
                                    .eq(SurveyRespondent::getSeq, seqValue));
                    //同步更新Prop表数据
                    CompletableFuture.runAsync(() -> surveyCommonService.saveRspdPropTaskWrapper(projectId, Integer.valueOf((String) seqValue), up_rspd, null, null, null, null, null), saveRspdPropTaskExecutor);
                    redisService.del("rspd_prop_" + projectId);
                    JSONArray jsonArray = JSON.parseArray(up_rspd.getQuotaCheckitemList());
                    if (ObjectUtil.isNotEmpty(jsonArray)) {
                        List<String> quotaCheckitemList = new ArrayList<>();
                        JSONArray quotaRespCheckitemList = new JSONArray();
                        for (int i = 0; i < jsonArray.size(); i++) {
                            quotaCheckitemList.add(jsonArray.getJSONObject(i).getString("quota_checkitem_id"));
                            quotaRespCheckitemList.add(jsonArray.getJSONObject(i).get("id"));
                        }
                        quotaCheckItemMapper.deleteBatchIds(quotaRespCheckitemList);

                        //发布删除配额消息
                        quotaCheckItemMapper.deleteBatchIds(quotaRespCheckitemList);
                        CreateQuotaDto mqdata = new CreateQuotaDto();
                        mqdata.setProjectId(projectId);
                        mqdata.setQuotaCheckitemIds(quotaCheckitemList);
                        surveyMessageSendService.surveyPopQuoteSend(mqdata);

                        List<String> quota_cond_ids = surveyQuotaCheckItemMapper.selectBatchIds(quotaCheckitemList)
                                .stream().map(SurveyQuotaCheckItem::getQuotaCondId).collect(Collectors.toList());
                        if (ObjectUtil.isNotEmpty(quota_cond_ids)) {
                            List<String> quota_id_list = surveyQuotaConditionMapper.selectBatchIds(quota_cond_ids)
                                    .stream().map(SurveyQuotaCondition::getQuotaId).collect(Collectors.toList());
                            quota_id_list.stream().filter(StrUtil::isNotBlank).forEach(quota_id -> surveyQuotaConditionService.refreshQuotaProgress(quota_id));
                        }
                    }
                    JSONObject data = new JSONObject();
                    data.put("seq", surveyRespondent.getSeq());
                    data.put("deliver_group_id", surveyRespondent.getDeliverGroupId());
                    data.put("deliver_id", surveyRespondent.getDeliverId());
                    data.put("member_id", surveyRespondent.getMemberId());
                    data.put("project_id", projectId);
                    data.put("dlvr_rid", surveyRespondent.getDlvrRid());
                    surveyMessageSendService.surveyRespondentUnfinishSend(data);
                }
            });
            map.put("discardCount", seqList.size());
        }

        return map;
    }

    private void fillRspdinfo(JSONObject jsonObject) {
        if (jsonObject.containsKey("begin_time") && jsonObject.containsKey("finish_time")) {
            jsonObject.put("time_cost", DateUtil.formatBetween(jsonObject.getDate("begin_time"), jsonObject.getDate("finish_time"), BetweenFormatter.Level.SECOND));
        }
        if (ObjectUtil.isNotEmpty(jsonObject.getString("user_agent"))) {
            UserAgent userAgent = UserAgentUtil.parse(jsonObject.getString("user_agent"));
            jsonObject.put("browser", userAgent.getBrowser() + "/" + userAgent.getVersion());
            jsonObject.put("os", userAgent.getOs() + "/" + userAgent.getOsVersion());
        }
        if (ObjectUtil.isNotEmpty(jsonObject.getString("group_id"))) {
            GroupDto groupDTO = userGrpcClient.getGroup(jsonObject.getString("group_id"));
            if (ObjectUtil.isNotEmpty(groupDTO)) {
                jsonObject.put("group", groupDTO.getTitle());
            } else {
                jsonObject.put("group", "全国");
            }
        } else {
            jsonObject.put("group", "全国");
        }
        if (ObjectUtil.isNotEmpty(jsonObject.getString("member_id"))) {
            MemberDto memberDto = memberGrpcClient.getMemberById(jsonObject.getString("member_id"));
            if (ObjectUtil.isNotEmpty(memberDto)) {
                String orgId = SecurityContextHolder.getOrgId();
                String userId = SecurityContextHolder.getUserId();
                boolean has_mdisplay = memberGrpcClient.checkDisplayRangePermission(orgId, userId, jsonObject.getString("member_id"));
                if (!has_mdisplay) {
                    jsonObject.put("m_email", ObjectUtil.isEmpty(memberDto.getEmail()) ? "空" : hideEmail(memberDto.getEmail(), true));
                    jsonObject.put("m_mobile", ObjectUtil.isEmpty(memberDto.getMobile()) ? "空" : hideMobile(memberDto.getMobile(), true));
                } else {
                    jsonObject.put("m_email", ObjectUtil.isEmpty(memberDto.getEmail()) ? "空" : memberDto.getEmail());
                    jsonObject.put("m_mobile", ObjectUtil.isEmpty(memberDto.getMobile()) ? "空" : memberDto.getMobile());
                }
                jsonObject.put("m_wechat", ObjectUtil.isEmpty(memberDto.getOpenId()) ? "空" : memberDto.getOpenId());
                jsonObject.put("m_alipay", ObjectUtil.isEmpty(memberDto.getAlipayUserId()) ? "空" : memberDto.getAlipayUserId());
                jsonObject.put("member_info", jsonObject.getString("m_mobile") + "/" + jsonObject.getString("m_email") +
                        "/" + jsonObject.getString("m_wechat") + "/" + jsonObject.getString("m_alipay"));
                JSONObject member_data = new JSONObject();
                member_data.put("m_mobile", jsonObject.getString("m_mobile"));
                member_data.put("m_email", jsonObject.getString("m_email"));
                member_data.put("m_wechat", jsonObject.getString("m_wechat"));
                member_data.put("m_alipay", jsonObject.getString("m_alipay"));
                member_data.put("m_gender", ObjectUtil.isEmpty(memberDto.getGender()) ? "空" : memberDto.getGender());
                jsonObject.put("member_data", member_data);
            }
        }
        if (ObjectUtil.isNotEmpty(jsonObject.getString("deliver_id"))) {
            DeliverDto deliver = deliverGrpcClient.getDeliver(jsonObject.getString("deliver_id"));
            if (ObjectUtil.isNotEmpty(deliver)) {
                jsonObject.put("deliver_source", deliver.getName());
                jsonObject.put("deliver_channel", getdeliverChannelName(deliver.getChannel().toString()));
            }
        }
    }

    private JSONObject fillRspdSimpleInfo(JSONObject jsonObject) {
        if (jsonObject.containsKey("begin_time") && jsonObject.containsKey("finish_time")) {
            jsonObject.put("time_cost", DateUtil.formatBetween(jsonObject.getDate("begin_time"), jsonObject.getDate("finish_time"), BetweenFormatter.Level.SECOND));
        }
        if (ObjectUtil.isNotEmpty(jsonObject.getString("user_agent"))) {
            UserAgent userAgent = UserAgentUtil.parse(jsonObject.getString("user_agent"));
            jsonObject.put("browser", userAgent.getBrowser() + "/" + userAgent.getVersion());
            jsonObject.put("os", userAgent.getOs() + "/" + userAgent.getOsVersion());
        }
        if (ObjectUtil.isNotEmpty(jsonObject.getString("group_id"))) {
            GroupDto groupDTO = userGrpcClient.getGroup(jsonObject.getString("group_id"));
            if (ObjectUtil.isNotEmpty(groupDTO)) {
                jsonObject.put("group", groupDTO.getTitle());
            } else {
                jsonObject.put("group", "全国");
            }
        } else {
            jsonObject.put("group", "全国");
        }
        if (ObjectUtil.isNotEmpty(jsonObject.getString("deliver_id"))) {
            DeliverDto deliver = deliverGrpcClient.getDeliver(jsonObject.getString("deliver_id"));
            if (ObjectUtil.isNotEmpty(deliver)) {
                jsonObject.put("deliver_source", deliver.getName());
                jsonObject.put("deliver_channel", getdeliverChannelName(deliver.getChannel().toString()));
            }
        }
        return jsonObject;
    }

    @Override
    public Double getScoreByAnswer(JSONObject param) {
        String projectId = param.getString("projectId");
        Integer version = param.getInteger("version");
        List<QstructsDto> qStructList = qdesGrpcClient.listQStruct(projectId, version);
        if (ObjectUtil.isEmpty(qStructList)) {
            return null;
        }
        //{"1": [{"o_gid": "2", "score": ""}, {"o_gid": "3", "score": ""}], "5": [{"o_gid": "6", "score": ""}, {"o_gid": "7", "score": ""}]}
        String scores = qStructList.get(0).getScores();
        if (StringUtil.isNullOrEmpty(scores)) {
            return null;
        }
        JSONObject gid_score_dict = getScoreGidDict(JSONObject.parseObject(scores), null);
        List<Question> questionList = qdesGrpcClient.getQuestionList(qStructList.get(0).getId());
        JSONObject qtype_dict = new JSONObject();
        for (Question question : questionList) {
            JSONObject data = JSONObject.parseObject(question.getData());
            qtype_dict.put(String.valueOf(question.getGid()), data.getString("qtype"));
        }
        AtomicReference<Double> final_score = new AtomicReference<>((double) 0);
        JSONObject answerobj = getAnswersBySeq(param);
        JSONObject answers = answerobj.getJSONObject("answers");
        answers.forEach((qgid, temp) -> {
            if (ObjectUtil.isEmpty(temp)) {
                return;
            }
            if ("single".equals(qtype_dict.getString(qgid))) {
                JSONArray ans = (JSONArray) temp;
                if (NumberUtil.isNumber(gid_score_dict.getString(qgid + "_" + ans.getString(0)))) {
                    final_score.updateAndGet(v -> v + Double.parseDouble(gid_score_dict.getString(qgid + "_" + ans.getString(0))));
                }
            }
            if ("multiple".equals(qtype_dict.getString(qgid))) {
                JSONArray ans = (JSONArray) temp;
                for (int i = 0; i < ans.size(); i++) {
                    JSONArray subans = ans.getJSONArray(i);
                    if (ObjectUtil.isEmpty(subans)) {
                        return;
                    }
                    if (NumberUtil.isNumber(gid_score_dict.getString(qgid + "_" + subans.getString(0)))) {
                        final_score.updateAndGet(v -> v + Double.parseDouble(gid_score_dict.getString(qgid + "_" + subans.getString(0))));
                    }
                }
            }
            if ("matrix_single".equals(qtype_dict.getString(qgid))) {
                JSONObject ans = (JSONObject) temp;
                ans.forEach((rgid, v) -> {
                    if (ObjectUtil.isEmpty(v)) {
                        return;
                    }
                    JSONArray subans = (JSONArray) v;
                    if (NumberUtil.isNumber(gid_score_dict.getString(qgid + "_" + subans.getString(0)))) {
                        final_score.updateAndGet(v1 -> v1 + Double.parseDouble(gid_score_dict.getString(qgid + "_" + subans.getString(0))));
                    }
                });
            }
            if ("matrix_multiple".equals(qtype_dict.getString(qgid))) {
                JSONObject ans = (JSONObject) temp;
                ans.forEach((rgid, v) -> {
                    JSONArray subans = (JSONArray) v;
                    for (int i = 0; i < subans.size(); i++) {
                        JSONArray olist = subans.getJSONArray(i);
                        if (ObjectUtil.isEmpty(olist)) {
                            return;
                        }
                        if (NumberUtil.isNumber(gid_score_dict.getString(qgid + "_" + olist.getString(0)))) {
                            final_score.updateAndGet(v1 -> v1 + Double.parseDouble(gid_score_dict.getString(qgid + "_" + olist.getString(0))));
                        }
                    }
                });
            }
        });
        return final_score.get();
    }

    /**
     * 隐藏邮箱中间几位
     *
     * @param email   邮箱
     * @param noThrow true：不抛出异常
     */
    @Override
    public String hideEmail(String email, boolean noThrow) {
        if (EmailUtil.validateEmail(email)) {
            if (noThrow) {
                return "";
            }
            throw new BusinessException(String.valueOf(ValidationErrorCodeEnum.EMAIL_ERROR.getCode()), String.format("email error: {%s}", email));
        }
        return String.format("****@%s", email.split("@")[1]);
    }

    /**
     * 隐藏手机号中间几位
     *
     * @param phone   手机号码
     * @param noThrow true：不抛出异常
     */
    @Override
    public String hideMobile(String phone, boolean noThrow) {
        if (!Validator.isMobile(phone)) {
            if (noThrow) {
                return "";
            }
            throw new BusinessException(String.valueOf(ValidationErrorCodeEnum.EMAIL_ERROR.getCode()), String.format("phone error: {%s}", phone));
        }
        return String.format("%s****%s", phone.substring(0, 3), phone.substring(8, phone.length() - 1));
    }

    @Override
    public String getdeliverChannelName(String key) {
        Map<String, String> map = new HashMap<>();
        map.put("0", "未知");
        map.put("1", "预览");
        map.put("2", "二维码");
        map.put("3", "短信");
        map.put("4", "微信");
        map.put("5", "支付宝");
        map.put("6", "邮件");
        map.put("7", "定制");
        map.put("8", "嵌入式投放");
        map.put("9", "二维码 (专用)");
        map.put("10", "样本投放");
        map.put("11", "自定义链接");
        map.put("12", "接口投放");
        map.put("13", "微信公众号");
        map.put("14", "微信小程序");
        map.put("15", "企业年金-年度配额");
        map.put("16", "职业年金-年度配额");
        return map.get(key);
    }

    @Override
    public JSONObject getScoreGidDict(JSONObject scores, QstructsDto qstructsDto) {
        JSONObject gid_score_dict = new JSONObject();
        if (ObjectUtil.isEmpty(scores)) {
            return gid_score_dict;
        }
        if (ObjectUtil.isEmpty(qstructsDto)) {
            scores.forEach((qgid, ogidlist) -> {
                JSONArray array = (JSONArray) ogidlist;
                array.forEach((a -> {
                    JSONObject ogid = (JSONObject) a;
                    if (ogid.containsKey("r_gid")) {
                        gid_score_dict.put(qgid + "_" + ogid.get("r_gid") + "_" + ogid.get("o_gid"), ogid.getString("score"));
                    } else {
                        gid_score_dict.put(qgid + "_" + ogid.get("o_gid"), ogid.getString("score"));
                    }
                }));
            });
        } else {
            JSONObject data = JSONObject.parseObject(qstructsDto.getData());
            Set<String> exists_gid = getQstructItemsGids(data);
            scores.forEach((qgid, ogidlist) -> {
                if (!exists_gid.contains(qgid)) {
                    return;
                }
                JSONArray array = (JSONArray) ogidlist;
                array.forEach((a -> {
                    JSONObject ogid = (JSONObject) a;
                    if (ogid.containsKey("r_gid")) {
                        if (exists_gid.contains(ogid.getString("r_gid")) && exists_gid.contains(ogid.getString("o_gid"))) {
                            gid_score_dict.put(qgid + "_" + ogid.get("r_gid") + "_" + ogid.get("o_gid"), ogid.getString("score"));
                        }
                    } else {
                        if (exists_gid.contains(ogid.getString("o_gid"))) {
                            gid_score_dict.put(qgid + "_" + ogid.get("o_gid"), ogid.getString("score"));
                        }
                    }
                }));
            });
        }
        return gid_score_dict;
    }

    @Override
    public Set<String> getQstructItemsGids(Object item) {
        Set<String> exists_gid = new HashSet<>();
        parseItem(exists_gid, item);
        return exists_gid;
    }

    @Override
    public JSONObject getRespondentsAnswers(String projectId, HttpServletRequest request) {
        String seq = request.getParameter("seq");
        String gid = request.getParameter("gid");
        JSONObject args = new JSONObject();
        args.put("updataFlay", true);
        args.put("gid", gid);
        JSONObject data = getRespondentsDetail(seq, args).getJSONObject("data");
        JSONArray answers = data.getJSONArray("answers");
        JSONArray answer_list = new JSONArray();
        for (Object answer : answers) {
            if (((JSONObject) answer).getString("gid").equals(gid)) {
                answer_list.add(answer);
            }
        }
        data.put("answers", answer_list);
        return data;
    }

    @Override
    public JSONObject putRespondentsAnswers(String projectId, JSONObject json_data) {
        String seq = json_data.getString("seq");
        SurveyRespondent rspd = surveyRespondentMapper.selectOne(new QueryWrapper<SurveyRespondent>().lambda()
                .eq(SurveyRespondent::getSeq, seq).eq(SurveyRespondent::getProjectId, projectId));
        if (rspd.getStatus() == RESPONDENT_STATUS_BEGIN.getNumber()) {
            throw new BusinessException("答卷进行中，无法修改答案");
        }
        String gid = json_data.getString("gid");
        JSONObject new_answer = json_data.getJSONObject("answer");
        Object reset_answer = json_data.get("reset_answer");
        return updateAnswers(projectId, seq, gid, new_answer, reset_answer);
    }

    @Override
    public Map<String, Integer> deleteRespondents(String id, JSONObject param) {
        Map<String, Integer> map = new HashMap<>();
        String projectId = param.getString("project_id");
        if (ObjectUtil.isEmpty(projectId)) {
            throw new BusinessException("project_id is required");
        }
        Project project = qdesGrpcClient.getProject(projectId);
        if (Objects.isNull(project)) {
            throw new BusinessException("Project[id={" + projectId + "}] not found");
        }
        if (!SecurityContextHolder.getOrgId().equals(project.getCompanyId())) {
            // 具有权限检查
            throw new BusinessException("no permission");
        }
        if (!StringUtil.isNullOrEmpty(id)) {
            map.put("deleteCount", surveyRespondentMapper.update(null, new UpdateWrapper<SurveyRespondent>().lambda()
                    .eq(SurveyRespondent::getProjectId, projectId)
                    .eq(SurveyRespondent::getId, id)
                    .eq(SurveyRespondent::getStatus, RESPONDENT_STATUS_DISCARD.getNumber())
                    .set(SurveyRespondent::getStatus, RespondentStatusEnum.RESPONDENT_STATUS_DELETE.getValue())));
            return map;
        }
        List<Object> seq_list = param.getJSONArray("seqList");
        if (ObjectUtil.isNotEmpty(seq_list)) {
            map.put("deleteCount", surveyRespondentMapper.update(null, new UpdateWrapper<SurveyRespondent>().lambda()
                    .eq(SurveyRespondent::getProjectId, projectId)
                    .in(SurveyRespondent::getSeq, seq_list)
                    .eq(SurveyRespondent::getStatus, RESPONDENT_STATUS_DISCARD.getNumber())
                    .set(SurveyRespondent::getStatus, RespondentStatusEnum.RESPONDENT_STATUS_DELETE.getValue())));
            //# 答卷彻底删除，给联系人足迹发送mq
            sendMqToMemberFootprint(seq_list, projectId);
            surveyRespondentPropDataMapper.update(null, new LambdaUpdateWrapper<SurveyRespondentPropData>()
                    .eq(SurveyRespondentPropData::getProjectId, projectId)
                    .in(SurveyRespondentPropData::getSeq, seq_list)
                    .set(SurveyRespondentPropData::getStatus, RespondentStatusEnum.RESPONDENT_STATUS_DELETE.getValue())
            );
            return map;
        }

        //清空回收站
        //TODO 按照条件批量删除 页面设置不了条件，暂时按照全部批量删除
        if (param.containsKey("excludeSeqList")) {
            //清空时，前端无法勾选某个具体的答卷，因此excludeSeqList始终为空
            List<Object> exc_seq_list = param.getJSONArray("excludeSeqList");
            map.put("deleteCount", surveyRespondentMapper.update(null, new UpdateWrapper<SurveyRespondent>().lambda()
                    .eq(SurveyRespondent::getProjectId, projectId)
                    .eq(SurveyRespondent::getStatus, RESPONDENT_STATUS_DISCARD.getNumber())
                    .set(SurveyRespondent::getStatus, RespondentStatusEnum.RESPONDENT_STATUS_DELETE.getValue())));
            return map;
        }

        return map;
    }

    @Override
    public Object postRespondentsById(String id, JSONObject param) {
//        String project_id = param.getString("project_id");
//        if (ObjectUtil.isEmpty(project_id)) {
//            SurveyRespondent result = surveyRespondentMapper.selectById(id);
//            if (ObjectUtil.isEmpty(result)) {
//                throw new BusinessException("Respondent[id={" + id + "}] not found");
//            }
//            project_id = result.getProjectId();
//        }
//        SurveyRespondent respondent = surveyRespondentMapper.selectOne(new LambdaQueryWrapper<SurveyRespondent>()
//                .eq(SurveyRespondent::getId, id)
//                .eq(SurveyRespondent::getProjectId, project_id)
//                .eq(SurveyRespondent::getStatus, param.getInteger("status")));
        SurveyRespondent respondent = surveyRespondentMapper.selectOne(new LambdaQueryWrapper<SurveyRespondent>()
                .eq(SurveyRespondent::getId, id)
                .eq(SurveyRespondent::getStatus, param.getInteger("status")));
        if (ObjectUtil.isEmpty(respondent)) {
            throw new BusinessException("Respondent[id={" + id + "}] not found");
        }
        Project proj = qdesGrpcClient.getProject(respondent.getProjectId());
        if (ObjectUtil.isEmpty(proj)) {
            throw new BusinessException("Project[id={" + id + "}] not found");
        }
        if (!SecurityContextHolder.getOrgId().equals(proj.getCompanyId())) {
            // 具有权限检查
            throw new BusinessException("no permission");
        }
        JSONObject rspd = JSONObject.parseObject(JSON.toJSONString(respondent));
        if (ObjectUtil.isNotEmpty(respondent.getDeliverId())) {
            DeliverDto deliver = deliverGrpcClient.getDeliver(respondent.getDeliverId());
            rspd.put("deliver_name", deliver.getName());
            if (ObjectUtil.isNotEmpty(param.get("withProject"))) {
                rspd.put("project_title", proj.getTitle());
                rspd.put("survey_title", getsurveytitle(respondent.getProjectId(), respondent.getVersion()));
            }
        }

        return rspd;
    }

    @Override
    public Object postRespondentsPage(JSONObject param) {
        //机构联系人详情页
        //TODO 回收站很多条件,这里不完整
        //{
        //    "page": 1,
        //    "rowsPerPage": 10,
        //    "project_id": "64f5a4dacff78909563cdd28",
        //    "status": 7,
        //    "source": "7,6,8",
        //    "groupID": "64be7978b3f85f634032b0fb,64be7978b3f85f634032b0fc",
        //    "version": "1",
        //    "ext_p": {},
        //    "qLogic": "or",
        //    "qList": [
        //        {
        //            "id": "64",
        //            "op": "eq",
        //            "qtype": "blank",
        //            "v": "11"
        //        }
        //    ],
        //    "deliver_id": "64f5b113f715856e747cb4f4,64f5a4def715856e747cb482,64f5a4def715856e747cb480,64f5b171f715856e747cb532"
        //}

        ArrayList<String> cols = Lists.newArrayList(
                "data_status",
                "status",
                "pre_discard_status",
                "seq",
                "version",
                "source",
                "client_type",
                "rspd_data_id",
                "rspd_weixin_id",
                "rspd_location_id",
                "rspd_extra_id",
                "rspd_attachment_id",
                "submit_seq",
                "survey_gids",
                "score");
        QueryWrapper<SurveyRespondent> queryWrapper = surveyRespondentService.getQueryWrapperEqOrIn(SurveyRespondent.class, param, cols);
        //HashMap<String, Object> map = new HashMap<>();
        Integer page = param.getInteger("page");
        Integer rowsPerPage = param.getInteger("rowsPerPage");
        SurveyRespondentPageVo pageVo = new SurveyRespondentPageVo();
        pageVo.setPage(page);
        Page<SurveyRespondent> surveyRespondentPage;
        if (param.containsKey("member_id")) {
            queryWrapper.eq(!ObjectUtils.isEmpty(param.getString("project_id")), "project_id", param.getString("project_id"));
            queryWrapper.eq(!ObjectUtils.isEmpty(param.getString("member_id")), "member_id", param.getString("member_id"));
            queryWrapper.orderByDesc("begin_time");
            surveyRespondentPage = surveyRespondentMapper.selectPage(new Page<>(page, rowsPerPage), queryWrapper);
        } else {
            queryWrapper.eq(!ObjectUtils.isEmpty(param.getString("project_id")), "project_id", param.getString("project_id"));
            queryWrapper.eq(!ObjectUtils.isEmpty(param.getString("member_id")), "member_id", param.getString("member_id"));
            queryWrapper.orderByDesc("version");
            queryWrapper.orderByDesc("begin_time");
            surveyRespondentPage = surveyRespondentMapper.selectPage(new Page<>(page, rowsPerPage), queryWrapper);
        }
        List<SurveyRespondent> surveyRespondentList = surveyRespondentPage.getRecords();
        if (CollUtil.isEmpty(surveyRespondentList)) {
            pageVo.setTotalCount(0);
            pageVo.setRows(new ArrayList<>());
            return pageVo;
        }
        List<SurveyRespondentVo> surveyRespondentVoList = new ArrayList<>();
        surveyRespondentList.forEach(a -> {
            SurveyRespondentVo vo = new SurveyRespondentVo();
            JSONObject rspd = JSONObject.parseObject(JSON.toJSONString(a));
            JSONObject result = fillRspdSimpleInfo(rspd);
            BeanUtil.copyProperties(a, vo);
            vo.setTimeCost(result.getString("time_cost"));
            vo.setGroupName(result.getString("group_name"));
            vo.setDeliverSource(result.getString("deliver_source"));
            vo.setDeliverChannel(result.getString("deliver_channel"));
            Project proj = qdesGrpcClient.getProject(a.getProjectId());
            if (param.containsKey("withProject") && param.getBoolean("withProject")) {
                vo.setProjectTitle(proj.getTitle());
                vo.setSurveyTitle(getsurveytitle(a.getProjectId(), a.getVersion()));
            }
            surveyRespondentVoList.add(vo);
        });

        //组装
        pageVo.setRows(surveyRespondentVoList);
        pageVo.setTotalCount(surveyRespondentPage.getTotal());
        return pageVo;
    }

    @Override
    public Object postRespondents(JSONObject param) {
        return null;
    }

    private JSONObject updateAnswers(String projectId, String seq, String gid, JSONObject new_answer, Object reset_answer) {
        JSONObject return_data = new JSONObject();
        SurveyRespondentData respondent_data = surveyRespondentDataMapper.selectOne(new QueryWrapper<SurveyRespondentData>().lambda()
                .eq(SurveyRespondentData::getSeq, seq).eq(SurveyRespondentData::getProjectId, projectId));
        JSONObject answers = JSONObject.parseObject(respondent_data.getAnswers());
        Object with_modifylog_old_answer = ObjectUtil.isNotEmpty(answers.get(gid)) ? answers.get(gid) : "";
        List<SurveyRespondentModifyLog> modify_log_list = surveyRespondentModifyLogMapper.selectList(new QueryWrapper<SurveyRespondentModifyLog>().lambda()
                .eq(SurveyRespondentModifyLog::getSeq, seq).eq(SurveyRespondentModifyLog::getGid, gid));

        if (ObjectUtil.isNotEmpty(reset_answer)) {
            if (resetAns(seq, gid) || modify_log_list.isEmpty()) {
                throw new BusinessException("Please Do not repetitive operation");
            }
            new_answer = JSONObject.parseObject(modify_log_list.get(0).getOldAnswer());
            answers.put(gid, new_answer.get(gid));
            return_data.put("seq", seq);
            return_data.put("gid", gid);
            return_data.put("answer", new_answer);
            return_data.put("updated_dt", DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now()));
        } else {
            return_data.put("seq", seq);
            return_data.put("gid", gid);
            return_data.put("answer", "");
            return_data.put("updated_dt", DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss").format(LocalDateTime.now()));

            redisService.del("reset_ans#" + seq + "#" + gid);
            if (ObjectUtil.isNotEmpty(new_answer)) {
                JSONObject modify_log_new_answer = ObjectUtil.isNotEmpty(modify_log_list) ?
                        JSONObject.parseObject(modify_log_list.get(modify_log_list.size() - 1).getNewAnswer()) : null;
                //# 如果修改的答案，与数据库中最后一次修改的答案不一致，则记录这次修改日志，反之直接跳过
                if (!new_answer.equals(modify_log_new_answer)) {
                    //# 将旧答案中对应的gid重新赋值成新的答案
                    //# 新答案为空的时候,取不到相关gid的答案会报错：
                    if (ObjectUtil.isNotEmpty(new_answer)) {
                        answers.put(gid, new_answer.get(gid));
                    }
                }
            } else {
                if (ObjectUtil.isNotEmpty(answers.get(gid))) {
                    answers.remove(gid);
                } else {
                    throw new BusinessException("Please Do not repetitive operation");
                }
            }
        }

        SurveyRespondent rspd = surveyRespondentMapper.selectOne(new QueryWrapper<SurveyRespondent>().lambda()
                .eq(SurveyRespondent::getSeq, seq).eq(SurveyRespondent::getProjectId, projectId));
        JSONObject oldAnswer = new JSONObject();
        oldAnswer.put(gid, with_modifylog_old_answer);
        SurveyRespondentModifyLog surveyRespondentModifyLog = new SurveyRespondentModifyLog();
        surveyRespondentModifyLog.setSeq(Long.valueOf(seq));
        surveyRespondentModifyLog.setProjectId(projectId);
        surveyRespondentModifyLog.setUserId(SecurityContextHolder.getUserId());
        surveyRespondentModifyLog.setGid(Integer.valueOf(gid));
        surveyRespondentModifyLog.setOldAnswer(String.valueOf(oldAnswer));
        surveyRespondentModifyLog.setNewAnswer(String.valueOf(new_answer));
        surveyRespondentModifyLog.setVersion(rspd.getVersion());
        surveyRespondentModifyLog.setUpdatedDt(LocalDateTime.now());
        //# RespondentModifyLog新增操作数据
        surveyRespondentModifyLogMapper.insert(surveyRespondentModifyLog);
        //# 更新RespondentData表中answers的数据
        surveyRespondentDataMapper.update(null, new UpdateWrapper<SurveyRespondentData>().lambda()
                .eq(SurveyRespondentData::getSeq, seq).eq(SurveyRespondentData::getProjectId, projectId).set(SurveyRespondentData::getAnswers, answers.toJSONString()));
        //无返回值异步任务
        CompletableFuture.runAsync(() -> surveyCommonService.saveRspdPropTaskWrapper(projectId, Integer.valueOf(seq), rspd, null, null, null, null, null), saveRspdPropTaskExecutor);
        surveyCommonService.mqSurveyRespondentModify(rspd, answers, JSONObject.parseObject(respondent_data.getCustomAnswers()));

        surveyRespondentMapper.update(null, new UpdateWrapper<SurveyRespondent>().lambda()
                .eq(SurveyRespondent::getProjectId, projectId).eq(SurveyRespondent::getSeq, seq).set(SurveyRespondent::getExcelParsed, 0));
        return return_data;
    }

    @XmCache
    private boolean resetAns(String seq, String gid) {
        List<SurveyRespondentModifyLog> rspd_modify_log_list = surveyRespondentModifyLogMapper.selectList(new QueryWrapper<SurveyRespondentModifyLog>().lambda()
                .eq(SurveyRespondentModifyLog::getSeq, seq).eq(SurveyRespondentModifyLog::getGid, gid));
        if (!rspd_modify_log_list.isEmpty()) {
            return rspd_modify_log_list.get(0).getOldAnswer().equals(rspd_modify_log_list.get(rspd_modify_log_list.size() - 1).getNewAnswer());
        }
        return false;
    }

    private void parseItem(Set<String> exists_gid, Object items) {
        if (items instanceof List) {
            JSONArray jsonArray = (JSONArray) items;
            for (Object i : jsonArray) {
                parseItem(exists_gid, i);
            }
        } else if (items instanceof Map) {
            JSONObject temp = (JSONObject) items;
            if (ObjectUtil.isNotEmpty((temp.get("gid")))) {
                exists_gid.add(temp.getString("gid"));
            }
            temp.forEach((k, v) -> parseItem(exists_gid, v));
        }
    }

    private JSONObject getAnswersBySeq(JSONObject param) {
        JSONObject answers = new JSONObject();
        JSONObject custom_answers = new JSONObject();
        if (param.getInteger("status") != RESPONDENT_STATUS_BEGIN.getNumber()
                && param.getInteger("status") != RESPONDENT_STATUS_OPEN.getNumber()) {
            SurveyRespondentData surveyRespondentData = getRespondentDataByRid(param.getString("id"), param.getString("projectId"));
            if (ObjectUtil.isNotEmpty(surveyRespondentData)) {
                answers = JSONObject.parseObject(surveyRespondentData.getAnswers());
                custom_answers = JSONObject.parseObject(surveyRespondentData.getCustomAnswers());
            }
        }
        if (ObjectUtil.isEmpty(answers)) {
            JSONObject orig_answers = surveyCommonService.getAnsweredData(Integer.parseInt(param.getString("seq")));
            String prefix = "custom_data__";
            int prelen = prefix.length();
            JSONObject finalAnswers = new JSONObject();
            JSONObject finalCustom_answers = new JSONObject();
            orig_answers.forEach((k, v) -> {
                if (NumberUtil.isNumber(k)) {
                    finalAnswers.put(k, v);
                }
                if (StrUtil.startWith(k, prefix)) {
                    finalCustom_answers.put(k.substring(prelen), v);
                }
            });
            answers.putAll(finalAnswers);
            custom_answers.putAll(finalCustom_answers);
        }
        JSONObject answerobj = new JSONObject();
        answerobj.put("answers", answers);
        answerobj.put("custom_answers", custom_answers);
        return answerobj;
    }

    @Override
    public String getsurveytitle(String projectId, Integer version) {
        List<QstructsDto> qStructList = qdesGrpcClient.listQStruct(projectId, version);
        if (ObjectUtil.isEmpty(qStructList)) {
            throw new BusinessException("QStruct[projectID={" + projectId + "},version={" + version + "}] not found");
        }
        JSONObject data = JSONObject.parseObject(qStructList.get(0).getData());
        if (ObjectUtil.isNotEmpty(data)) {
            if (ObjectUtil.isNotEmpty(data.get("desc_begin_page"))) {
                JSONObject desc_begin_page = data.getJSONObject("desc_begin_page");
                if (ObjectUtil.isNotEmpty(desc_begin_page.getJSONObject("content"))) {
                    String text = desc_begin_page.getJSONObject("content").getString("text");
                    return HtmlUtil.cleanHtmlTag(text);
                }
            }
        }
        return null;
    }

    @Override
    public SurveyRespondentData getRespondentDataByRid(String rid, String projectId) {
        return surveyRespondentDataMapper.selectOne(new QueryWrapper<SurveyRespondentData>().lambda()
                .eq(SurveyRespondentData::getProjectId, projectId)
                .eq(SurveyRespondentData::getRid, rid)
                .last("limit 1"));
    }

    @Override
    public JSONObject getUidsSensitives(String projectId, String userId) {
        JSONObject sensitive_map = new JSONObject();
        if (ObjectUtil.isEmpty(userId)) {
            return sensitive_map;
        }
        List<ProjectExtraAttr> projectExtraAttrList = qdesGrpcClient.listProjectExtraAttr(null, projectId);
        if (ObjectUtil.isEmpty(projectExtraAttrList)) {
            return sensitive_map;
        }
        JSONObject sensitive_words = JSONObject.parseObject(projectExtraAttrList.get(0).getCustomAttr()).getJSONObject("sensitive");
        if (ObjectUtil.isEmpty(sensitive_words)) {
            return sensitive_map;
        }
        JSONArray roleList = new JSONArray();
        if (ObjectUtil.isNotEmpty(userId)) {
            List<UserRole> userRoles = userGrpcClient.getUserRole(userId, null);
            for (UserRole userRole : userRoles) {
                roleList.add(userRole.getRoleId());
            }
        }
        roleList.add(userId);
        sensitive_map.put("roleList", roleList);
        sensitive_map.put("sensitive_words", sensitive_words);
        return sensitive_map;
    }

    @Override
    public Object parseAnswers(JSONObject param, QdesProject project) {
        boolean whole_qinfo = param.getBooleanValue("whole_qinfo");
        JSONArray answer_list = new JSONArray();
        if (ObjectUtil.isEmpty(param.get("project"))) {
            project = qdesGrpcClient.getReleasedProject(param.getString("projectId"), param.getInteger("version"));
            if (ObjectUtil.isEmpty(project)) {
                JSONObject ObjectDict = new JSONObject();
                ObjectDict.put("data", new JSONObject());
                return ObjectDict;
            }
        }
        String custom_cid = JSONObject.parseObject(project.getCustomAttr()).getString("openCustomCid");
        param.put("custom_cid", custom_cid);
        param.put("answer_list", answer_list);
        JSONObject qstruct = JSONObject.parseObject(project.getQstruct());
        JSONArray items = qstruct.getJSONObject("data").getJSONArray("items");
        param.put("items", items);

        parseAnswerItem(param, 1);
        if (whole_qinfo) {
            qstruct.getJSONObject("data").put("items", answer_list);
            return qstruct;
        }
        return answer_list;
    }

    private JSONArray getAttachments(JSONObject param) {
        JSONObject attach_dict = new JSONObject();
        JSONObject jsonObject = new JSONObject();
        List<SurveyRespondentAttachment> attach_list = getAttachments(param.getString("projectId"), param.getString("seq"));
        for (SurveyRespondentAttachment att : attach_list) {
            Integer att_option_id = ObjectUtil.isNotEmpty(att.getOptionId()) ? att.getOptionId() : 0;
            JSONObject objectDict = new JSONObject();
            objectDict.put("url", att.getUrl());
            objectDict.put("size", att.getSize());
            objectDict.put("ftype", att.getFtype());
            objectDict.put("name", att.getName());
            JSONArray jsonArray = new JSONArray();
            jsonArray.add(objectDict);
            jsonObject.put(String.valueOf(att_option_id), jsonArray);
            attach_dict.put(String.valueOf(att.getQid()), jsonObject);
        }
        return attach_dict.getJSONObject(param.getString("qid")).getJSONArray(String.valueOf(ObjectUtil.isEmpty(param.getString("optionId")) ? 0 : 1));
    }

    private JSONObject checkSensitive(JSONObject item, JSONObject param) {
        if (ObjectUtil.isNotEmpty(param.getJSONObject("sensitive_map"))) {
            if (checkQuestionSensitive(param.getJSONObject("sensitive_map"), item.getString("gid"))) {
                item.put("answer", new JSONArray());
                item.put("sensitive", true);
            } else {
                item.put("sensitive", false);
            }
        }
        return item;
    }

    @Override
    public boolean checkQuestionSensitive(JSONObject sensitive_map, String gid) {
        if (ObjectUtil.isEmpty(sensitive_map)) {
            return false;
        }
        //{"sensitive": {"2": {"ids": ["62a9756caace70000ad29a55", "63a3d2c62c549648f7c2652f"], "stype": 1}}}
        List<Object> roleList = sensitive_map.getJSONArray("roleList");
        JSONObject uiddict = sensitive_map.getJSONObject("sensitive_words").getJSONObject(gid);
        if (ObjectUtil.isEmpty(uiddict)) {
            return false;
        }
        List<Object> ids = uiddict.getJSONArray("ids");

        return !CollUtil.intersection(roleList, ids).isEmpty();

    }

    @Override
    public Integer parseAnswerItem(JSONObject param, Integer index) {
        Integer item_cnt = 0;
        for (JSONObject item : param.getJSONArray("items").toJavaList(JSONObject.class)) {
            param.put("data", item);
            if (!item.containsKey("type")) {
                log.info(item.toJSONString());
            }
            if ("G".equals(item.getString("type"))) {
                param.put("items", item.getJSONArray("items"));
                Integer cnt = parseAnswerItem(param, index);
                item_cnt += cnt;
                index += cnt;
            } else if ("Q".equals(item.getString("type"))) {
                String gid = item.getString("gid");
                JSONArray attach_list = new JSONArray();
                boolean opt_attach_getter = false;
                if (param.getBooleanValue("with_attach") && item.containsKey("attachData")) {
                    if (ObjectUtil.isNotEmpty(item.getJSONObject("attachData").get("openAttach"))) {
                        param.put("qid", gid);
                        attach_list = getAttachments(param);
                    }
                    if (ObjectUtil.isNotEmpty(item.getJSONObject("attachData").get("openOAttach"))) {
                        opt_attach_getter = true;
                    }
                }
                param.put("attachment_getter", opt_attach_getter);
                //answers {"2": [1], "6": [4], "9": {"1": "浙江省", "2": "杭州市", "3": "高新开发区（滨江）"}, "11": [2], "13": [2], "25": {"1": [9, "", ""]}, "27": {"1": [2, "", ""]}, "29": {"1": [3, "", ""]}, "31": {"1": [4, "", ""]}, "33": {"1": [4, "", ""]}}
                Object answer = param.getJSONObject("answers").get(gid);
                param.put("answer", answer);
                if (param.getBooleanValue("whole_qinfo")) {
                    if (ObjectUtil.isNotEmpty(answer)) {
                        parseQuestion(param);
                    }
                    if (ObjectUtil.isNotEmpty(attach_list)) {
                        item.put("attachments", attach_list);
                    }
                    checkSensitive(item, param);
                    param.getJSONArray("answer_list").add(item);
                } else {
                    if (ObjectUtil.isNotEmpty(answer)) {
                        JSONObject ans = parseQuestion(param);
                        param.put("ans", ans);
                        ans = isClearAnswer(param);
                        if (ObjectUtil.isNotEmpty(attach_list)) {
                            ans.put("attachments", attach_list);
                        }
                        checkSensitive(ans, param);
                        param.getJSONArray("answer_list").add(ans);
                    }
                }
                item_cnt += 1;
                index += 1;
            }
        }
        return item_cnt;
    }

    private JSONObject isClearAnswer(JSONObject param) {
        JSONObject ans = param.getJSONObject("ans");
        if (ObjectUtil.hasEmpty(param.getString("projectId"), param.getString("seq"), param.getString("gid"))) {
            return param.getJSONObject("ans");
        }
        if (ObjectUtil.isNotEmpty(ans) && ObjectUtil.isNotEmpty(ans.get("undisplay")) && ans.getBoolean("undisplay")) {
            Long count = surveyRespondentModifyLogMapper.selectCount(new QueryWrapper<SurveyRespondentModifyLog>().lambda().eq(SurveyRespondentModifyLog::getGid, param.getString("gid"))
                    .eq(SurveyRespondentModifyLog::getGid, param.getString("projectId"))
                    .eq(SurveyRespondentModifyLog::getGid, param.getString("seq")));
            if (count == 0) {
                ans.remove("undisplay");
                if (!ans.containsKey("answer")) {
                    ans.put("answer", "");
                }
            }
        }
        return ans;
    }

    /**
     * 解析单个题目答案
     *
     * @param param
     * @return
     */
    private JSONObject parseQuestion(JSONObject param) {
        JSONObject data = param.getJSONObject("data");
        String qtype = data.getString("qtype");
        Object parser = QUESTION_PARSERS.get(qtype).apply(param);
        if (ObjectUtil.isEmpty(parser) && !"descriptor".equals(qtype)) {
            throw new BusinessException("cannot find parser for qtype=" + qtype + "data");
        }
        String cid = data.getString("cid");
        if (param.getBooleanValue("whole_qinfo")) {
            data.put("_index", cid);
            data.put("cid", cid);
            if (ObjectUtil.isNotEmpty(param.get("answer"))) {
                QUESTION_PARSERS.get(qtype).apply(param);
            }

        }
        JSONObject ret = new JSONObject();
        ret.put("gid", data.getString("gid"));
        ret.put("cid", cid);
        ret.put("_index", cid);
        ret.put("title", data.getString("title"));
        ret.put("qtype", data.getString("qtype"));
        ret.put("custom_qtype", ObjectUtil.isNotEmpty(data.getString("custom_qtype")) ? data.getString("custom_qtype") : "");
        ret.put("current_template", ObjectUtil.isNotEmpty(data.get("current_template")) ? data.get("current_template") : new JSONObject());
        if (ObjectUtil.isEmpty(param.get("answer"))) {
            if (!"descriptor".equals(qtype)) {
                ret.put("undisplay", true);
            }
            if ("auto_table".equals(qtype)) {
                ret.put("answer", new JSONArray());
            }
        } else {
            param.put("whole_qinfo", false);
            Object tmp_ans = QUESTION_PARSERS.get(qtype).apply(param);
            if (ObjectUtil.isEmpty(tmp_ans)) {
                ret.put("undisplay", true);
            }
            ret.put("answer", tmp_ans);
        }
        return ret;
    }

    @Override
    public JSONArray parseCustomAnswers(String customAnswers) {
        String orgId = SecurityContextHolder.getOrgId();
        JSONArray ext = new JSONArray();
        JSONObject ext_dict = new JSONObject();
        JSONObject enum_ext_params = surveyCommonService.getEnumExtParams(orgId, EXTPARAMENUMMANAGE_STATUS_START_VALUE);
        JSONObject query = new JSONObject();
        query.put("org_id", orgId);
        query.put("status", Lists.newArrayList(DELIVEREXTPARAMS_STATUS_START_VALUE));
        List<JSONObject> ext_params_desc_map = surveyCommonService.listExtparam(query);
        query.put("dtype", EXTPARAM_DTYPE_DATA_TIMESTAMP.getValue());
        List<JSONObject> ext_timestamp_params = surveyCommonService.listExtparam(query);
        query.put("dtype", EXTPARAM_DTYPE_DATA_BOOL.getValue());
        List<JSONObject> bool_ext_params = surveyCommonService.listExtparam(query);

        JSONObject.parseObject(customAnswers).forEach((key, val) -> {
            if (!key.startsWith("custom_data__")) {
                if (ObjectUtil.isNotEmpty(enum_ext_params.get(key))) {
                    ext_dict.put("name", key);
                    ext_dict.put("v", enum_ext_params.getJSONObject(key).getString((String) val));
                } else if (ext_timestamp_params.stream().anyMatch(it -> StrUtil.equals(key, it.getString("name")))) {
                    if (ObjectUtil.isNotEmpty(val)) {
                        ext_dict.put("name", DateUtil.format((LocalDateTime) val, "yyyy-MM-dd HH:mm:ss"));
                    } else {
                        ext_dict.put("name", key);
                        ext_dict.put("v", "");
                    }
                } else if (bool_ext_params.stream().anyMatch(it -> StrUtil.equals(key, it.getString("name")))) {
                    if (ObjectUtil.isEmpty(val)) {
                        ext_dict.put("name", key);
                        ext_dict.put("v", "");
                    } else if ("true".equals(val) || "1".equals(val) || "True".equals(val)) {
                        ext_dict.put("name", key);
                        ext_dict.put("v", 1);
                    } else {
                        ext_dict.put("name", key);
                        ext_dict.put("v", 0);
                    }
                } else {
                    ext_dict.put("name", key);
                    ext_dict.put("v", val);
                }
                ext_params_desc_map.forEach(a -> {
                    if (StrUtil.equals(key, a.getString("name"))) {
                        ext_dict.put("desc", a.getString("desc"));
                    }
                });
                JSONObject ext_dict_temp = new JSONObject();
                ext_dict_temp.putAll(ext_dict);
                ext.add(ext_dict_temp);
            }
        });
        return ext;
    }

    /**
     * 对于单选题： 返回[选项id]
     * 对于多选题： 返回[选项1id, 选项2id...]
     * 对于打分题： 返回[分值]
     * 对于排序题： 返回[序号]
     * 对于级联题： 返回对应级的[选项id]
     * 对于城市题： 返回对应级的城市名称
     */
    private JSONArray getOptions(Object answer) {
        JSONArray options = new JSONArray();
        if (answer instanceof Integer || answer instanceof String) {
            options.add(answer);
        }
        if (answer instanceof List) {
            for (Object ops : (JSONArray) answer) {
                if (ops instanceof List) {
                    options.add(((List<?>) ops).get(0));
                } else if (ops instanceof Integer) {
                    options.add(ops);
                }
            }
        }
        return options;
    }

    private void sendMqToMemberFootprint(List<Object> seq_list, String pid) {
        for (Object seq : seq_list) {
            SurveyRespondent rspd = surveyRespondentMapper.selectOne(new LambdaQueryWrapper<SurveyRespondent>()
                    .eq(SurveyRespondent::getSeq, seq)
                    .eq(SurveyRespondent::getProjectId, pid));
            SurveyRespondentData rspd_data = surveyRespondentDataMapper.selectOne(new LambdaQueryWrapper<SurveyRespondentData>()
                    .eq(SurveyRespondentData::getSeq, seq)
                    .eq(SurveyRespondentData::getProjectId, pid));
            JSONObject total_answer = JSONObject.parseObject(rspd_data.getAnswers());
            JSONObject custom_answer = JSONObject.parseObject(rspd_data.getCustomAnswers());
            surveyCommonService.memberFootPrintMq(rspd, total_answer, custom_answer);
        }
    }
}




